instances exist — same idea as the per-instance scripts
-# in dialog.py, just shared instead of inlined per node.
-#
-# Opt-in hooks (all via data-* attributes, none required):
-# [data-dt-search-for="tableId"] search input, anywhere on the page —
-# see TableSearch / table.search()
-# [data-dt-sort="key"] sortable header button (paired with a
-# `data-sort-value` attr on the matching
-# , or falls back to that cell's text)
-# [data-dt-prev] / [data-dt-next] / [data-dt-info] / [data-dt-page-label]
-# pagination controls
-# [data-dt-select-all] header "select all" checkbox
-# [data-dt-select] per-row checkbox — must have a
-# unique `value` (e.g. a row id)
-# --------------------------------------------------------------------------
-
-
DATA_TABLE_JS = r"""
if (typeof window !== "undefined") {
window.__dataTable = window.__dataTable || {
diff --git a/assets/docs/getting-started/cli.md b/assets/docs/getting-started/cli.md
index ed8b7ac..4a81922 100644
--- a/assets/docs/getting-started/cli.md
+++ b/assets/docs/getting-started/cli.md
@@ -41,6 +41,7 @@ buridan apply --preset
```
Arguments:
+
- `--preset`: The theme preset ID from the Buridan UI theme builder. Use `b0` for the default theme.
Example:
@@ -85,8 +86,8 @@ buridan list
# Recommended workflow
```bash
-buridan create # build your theme, copy the preset ID
-buridan init # set up utilities and Tailwind config
-buridan apply --preset # apply your theme
-buridan add button input select # add the components you need
+buridan create
+buridan init
+buridan apply --preset
+buridan add button input select
```
diff --git a/assets/globals.css b/assets/globals.css
index e2b60f1..9081ab7 100644
--- a/assets/globals.css
+++ b/assets/globals.css
@@ -164,8 +164,8 @@
prose-h2:scroll-mt-14
prose-h1:text-2xl
prose-h1:font-bold
- prose-h1:tracking-tight
- prose-h1:mb-3
+ prose-h1:tracking-tighter
+ prose-h1:mb-1
prose-h2:text-xl
prose-h2:font-semibold
prose-h2:tracking-tight
@@ -175,7 +175,7 @@
/* 4. Paragraph Modifiers */
@apply prose-p:text-muted-foreground
- prose-p:leading-relaxed;
+ prose-p:leading-relaxed prose-p:text-sm;
/* 5. Preformatted Code Block Modifiers */
@apply prose-pre:rounded-lg
diff --git a/assets/llms.txt b/assets/llms.txt
index d1f85b7..71075c3 100644
--- a/assets/llms.txt
+++ b/assets/llms.txt
@@ -38,6 +38,7 @@
- [Marker](https://native.buridan.dev/docs/components/marker): The Marker component.
- [Menu](https://native.buridan.dev/docs/components/menu): The Menu component.
- [Message](https://native.buridan.dev/docs/components/message): The Message component.
+- [Radio Group](https://native.buridan.dev/docs/components/radio-group): The Radio Group component.
- [Select](https://native.buridan.dev/docs/components/select): The Select component.
- [Separator](https://native.buridan.dev/docs/components/separator): The Separator component.
- [Spinner](https://native.buridan.dev/docs/components/spinner): The Spinner component.
diff --git a/assets/social/accordion.webp b/assets/social/accordion.webp
index 22e797e..c2e8cec 100644
Binary files a/assets/social/accordion.webp and b/assets/social/accordion.webp differ
diff --git a/assets/social/area-chart.webp b/assets/social/area-chart.webp
index 8170d1c..af792b4 100644
Binary files a/assets/social/area-chart.webp and b/assets/social/area-chart.webp differ
diff --git a/assets/social/attachment.webp b/assets/social/attachment.webp
index 6ba743f..19ef4c3 100644
Binary files a/assets/social/attachment.webp and b/assets/social/attachment.webp differ
diff --git a/assets/social/avatar.webp b/assets/social/avatar.webp
index 0590799..38f0c88 100644
Binary files a/assets/social/avatar.webp and b/assets/social/avatar.webp differ
diff --git a/assets/social/badge.webp b/assets/social/badge.webp
index 9446ee5..cca9a85 100644
Binary files a/assets/social/badge.webp and b/assets/social/badge.webp differ
diff --git a/assets/social/bar-chart.webp b/assets/social/bar-chart.webp
index 9568f97..9b59c02 100644
Binary files a/assets/social/bar-chart.webp and b/assets/social/bar-chart.webp differ
diff --git a/assets/social/blocks.webp b/assets/social/blocks.webp
index a2fcbb8..eefcfbe 100644
Binary files a/assets/social/blocks.webp and b/assets/social/blocks.webp differ
diff --git a/assets/social/bubble.webp b/assets/social/bubble.webp
index 89fb011..98143b6 100644
Binary files a/assets/social/bubble.webp and b/assets/social/bubble.webp differ
diff --git a/assets/social/button-group.webp b/assets/social/button-group.webp
index 24543a6..7e99060 100644
Binary files a/assets/social/button-group.webp and b/assets/social/button-group.webp differ
diff --git a/assets/social/button.webp b/assets/social/button.webp
index 16160c8..7507f58 100644
Binary files a/assets/social/button.webp and b/assets/social/button.webp differ
diff --git a/assets/social/card.webp b/assets/social/card.webp
index 762b6c6..cc48d58 100644
Binary files a/assets/social/card.webp and b/assets/social/card.webp differ
diff --git a/assets/social/changelog.webp b/assets/social/changelog.webp
index c8775c2..95f9cda 100644
Binary files a/assets/social/changelog.webp and b/assets/social/changelog.webp differ
diff --git a/assets/social/chart.webp b/assets/social/chart.webp
index b552a71..a8d8e81 100644
Binary files a/assets/social/chart.webp and b/assets/social/chart.webp differ
diff --git a/assets/social/charts.webp b/assets/social/charts.webp
index 98761d0..d7f7b5c 100644
Binary files a/assets/social/charts.webp and b/assets/social/charts.webp differ
diff --git a/assets/social/checkbox.webp b/assets/social/checkbox.webp
index 1d1fd57..708d9cf 100644
Binary files a/assets/social/checkbox.webp and b/assets/social/checkbox.webp differ
diff --git a/assets/social/cli.webp b/assets/social/cli.webp
index e0c3a6e..b93af59 100644
Binary files a/assets/social/cli.webp and b/assets/social/cli.webp differ
diff --git a/assets/social/client-state-var.webp b/assets/social/client-state-var.webp
index de57b6f..cdf5b59 100644
Binary files a/assets/social/client-state-var.webp and b/assets/social/client-state-var.webp differ
diff --git a/assets/social/collapsible.webp b/assets/social/collapsible.webp
index dd655d9..1988160 100644
Binary files a/assets/social/collapsible.webp and b/assets/social/collapsible.webp differ
diff --git a/assets/social/components.webp b/assets/social/components.webp
index 09b0b3f..8afc669 100644
Binary files a/assets/social/components.webp and b/assets/social/components.webp differ
diff --git a/assets/social/create.webp b/assets/social/create.webp
index 8e4bf27..3cf94b5 100644
Binary files a/assets/social/create.webp and b/assets/social/create.webp differ
diff --git a/assets/social/dashboards.webp b/assets/social/dashboards.webp
index 1f8d72f..3741a04 100644
Binary files a/assets/social/dashboards.webp and b/assets/social/dashboards.webp differ
diff --git a/assets/social/dev.webp b/assets/social/dev.webp
index fa6fbe3..d3745b1 100644
Binary files a/assets/social/dev.webp and b/assets/social/dev.webp differ
diff --git a/assets/social/dialog.webp b/assets/social/dialog.webp
index e9c1791..a5d00aa 100644
Binary files a/assets/social/dialog.webp and b/assets/social/dialog.webp differ
diff --git a/assets/social/docs.webp b/assets/social/docs.webp
index bf7b272..a198799 100644
Binary files a/assets/social/docs.webp and b/assets/social/docs.webp differ
diff --git a/assets/social/events.webp b/assets/social/events.webp
index 9b6db06..7dd531b 100644
Binary files a/assets/social/events.webp and b/assets/social/events.webp differ
diff --git a/assets/social/field.webp b/assets/social/field.webp
index 862f991..07216e3 100644
Binary files a/assets/social/field.webp and b/assets/social/field.webp differ
diff --git a/assets/social/frame.webp b/assets/social/frame.webp
index fdd4e99..6cf1b25 100644
Binary files a/assets/social/frame.webp and b/assets/social/frame.webp differ
diff --git a/assets/social/index.webp b/assets/social/index.webp
index 8bd50a7..baef1dd 100644
Binary files a/assets/social/index.webp and b/assets/social/index.webp differ
diff --git a/assets/social/input-group.webp b/assets/social/input-group.webp
index 3aaebc8..84cd8e3 100644
Binary files a/assets/social/input-group.webp and b/assets/social/input-group.webp differ
diff --git a/assets/social/input.webp b/assets/social/input.webp
index ab026d1..3be76f1 100644
Binary files a/assets/social/input.webp and b/assets/social/input.webp differ
diff --git a/assets/social/installation.webp b/assets/social/installation.webp
index 38eab2b..f1f6a7f 100644
Binary files a/assets/social/installation.webp and b/assets/social/installation.webp differ
diff --git a/assets/social/introduction.webp b/assets/social/introduction.webp
index 1ab1b2e..6defae4 100644
Binary files a/assets/social/introduction.webp and b/assets/social/introduction.webp differ
diff --git a/assets/social/javascript.webp b/assets/social/javascript.webp
index 92e37f1..d555d5d 100644
Binary files a/assets/social/javascript.webp and b/assets/social/javascript.webp differ
diff --git a/assets/social/line-chart.webp b/assets/social/line-chart.webp
index 1175203..5b94f60 100644
Binary files a/assets/social/line-chart.webp and b/assets/social/line-chart.webp differ
diff --git a/assets/social/markdown.webp b/assets/social/markdown.webp
index d3047ac..49acb55 100644
Binary files a/assets/social/markdown.webp and b/assets/social/markdown.webp differ
diff --git a/assets/social/marker.webp b/assets/social/marker.webp
index 3446984..b9ffa82 100644
Binary files a/assets/social/marker.webp and b/assets/social/marker.webp differ
diff --git a/assets/social/menu.webp b/assets/social/menu.webp
index cf90205..e5f7687 100644
Binary files a/assets/social/menu.webp and b/assets/social/menu.webp differ
diff --git a/assets/social/message.webp b/assets/social/message.webp
index e95ccfd..1bd13b0 100644
Binary files a/assets/social/message.webp and b/assets/social/message.webp differ
diff --git a/assets/social/radio-group.webp b/assets/social/radio-group.webp
new file mode 100644
index 0000000..17d357c
Binary files /dev/null and b/assets/social/radio-group.webp differ
diff --git a/assets/social/scroll-fade.webp b/assets/social/scroll-fade.webp
index 1973ebf..b1d16ce 100644
Binary files a/assets/social/scroll-fade.webp and b/assets/social/scroll-fade.webp differ
diff --git a/assets/social/select.webp b/assets/social/select.webp
index 45158fb..557ecaa 100644
Binary files a/assets/social/select.webp and b/assets/social/select.webp differ
diff --git a/assets/social/separator.webp b/assets/social/separator.webp
index c0926d0..a53776d 100644
Binary files a/assets/social/separator.webp and b/assets/social/separator.webp differ
diff --git a/assets/social/shimmer.webp b/assets/social/shimmer.webp
index 975b42f..1dad11d 100644
Binary files a/assets/social/shimmer.webp and b/assets/social/shimmer.webp differ
diff --git a/assets/social/skills.webp b/assets/social/skills.webp
index f6e5f40..b31e9f3 100644
Binary files a/assets/social/skills.webp and b/assets/social/skills.webp differ
diff --git a/assets/social/spinner.webp b/assets/social/spinner.webp
index 9b15297..62d69e6 100644
Binary files a/assets/social/spinner.webp and b/assets/social/spinner.webp differ
diff --git a/assets/social/state.webp b/assets/social/state.webp
index aca81cb..d228d88 100644
Binary files a/assets/social/state.webp and b/assets/social/state.webp differ
diff --git a/assets/social/switch.webp b/assets/social/switch.webp
index 5efd8d5..89ec2e9 100644
Binary files a/assets/social/switch.webp and b/assets/social/switch.webp differ
diff --git a/assets/social/table.webp b/assets/social/table.webp
index c05b58f..3e741fc 100644
Binary files a/assets/social/table.webp and b/assets/social/table.webp differ
diff --git a/assets/social/tabs.webp b/assets/social/tabs.webp
index 6504366..7785440 100644
Binary files a/assets/social/tabs.webp and b/assets/social/tabs.webp differ
diff --git a/assets/social/textarea.webp b/assets/social/textarea.webp
index 57da22d..05adaae 100644
Binary files a/assets/social/textarea.webp and b/assets/social/textarea.webp differ
diff --git a/assets/social/theming.webp b/assets/social/theming.webp
index 5155dbf..78d5099 100644
Binary files a/assets/social/theming.webp and b/assets/social/theming.webp differ
diff --git a/assets/social/typeset.webp b/assets/social/typeset.webp
index 01e5a82..5725ba5 100644
Binary files a/assets/social/typeset.webp and b/assets/social/typeset.webp differ
diff --git a/assets/social/typography.webp b/assets/social/typography.webp
index b5af496..cfca100 100644
Binary files a/assets/social/typography.webp and b/assets/social/typography.webp differ
diff --git a/components/ui/field.py b/components/ui/field.py
index a2e8a00..d7c09e1 100644
--- a/components/ui/field.py
+++ b/components/ui/field.py
@@ -29,7 +29,7 @@ class ClassNames:
FIELD_CONTENT = "group/field-content flex flex-1 flex-col gap-0.5 leading-snug"
- FIELD_LABEL = "group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border border-input *:data-[slot=field]:p-2.5 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10 has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col"
+ FIELD_LABEL = "group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-[:checked]:border-primary/30 has-[:checked]:bg-primary/5 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border border-input *:data-[slot=field]:p-2.5 dark:has-[:checked]:border-primary/20 dark:has-[:checked]:bg-primary/10 has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col"
FIELD_TITLE = "flex w-fit items-center gap-2 text-sm font-medium group-data-[disabled=true]/field:opacity-50"
diff --git a/components/ui/menu.py b/components/ui/menu.py
index 0d254ed..749f82f 100644
--- a/components/ui/menu.py
+++ b/components/ui/menu.py
@@ -129,27 +129,21 @@ def create(cls, *children, close_on_click: bool = True, **props) -> rx.Component
props["data-slot"] = "menu-item"
cls.set_class_name(ClassNames.ITEM, props)
- # 0. Pop out the variant prop so we can apply the data-variant attribute
variant = props.pop("variant", "default")
- props["data-variant"] = (
- variant # This maps to your Tailwind: data-[variant=destructive]
- )
+ props["data-variant"] = variant
item_id = props.get("id") or f"menu-item-{next(_menu_item_counter)}"
props["id"] = item_id
- # 1. Pop out any existing user on_click triggers
user_on_click = props.pop("on_click", None)
click_events = []
- # 2. Append the user's custom event(s) if they provided any
if user_on_click is not None:
if isinstance(user_on_click, list):
click_events.extend(user_on_click)
else:
click_events.append(user_on_click)
- # 3. If close_on_click is active, append the parent-closing script
if close_on_click:
close_script = rx.call_script(
f"""
@@ -160,7 +154,6 @@ def create(cls, *children, close_on_click: bool = True, **props) -> rx.Component
)
click_events.append(close_script)
- # 4. Bind the combined chain back to props if there's anything to execute
if click_events:
props["on_click"] = click_events
diff --git a/components/ui/radio_group.py b/components/ui/radio_group.py
new file mode 100644
index 0000000..ec9c296
--- /dev/null
+++ b/components/ui/radio_group.py
@@ -0,0 +1,94 @@
+import reflex as rx
+from reflex.components.component import ComponentNamespace
+
+from ..core.core import CoreComponent, cn
+
+
+class ClassNames:
+ ROOT = "w-full flex flex-col gap-3 disabled:opacity-50 disabled:cursor-not-allowed text-sm"
+
+ ITEM_ROOT = (
+ "group relative inline-flex size-4 shrink-0 cursor-pointer items-center "
+ "justify-center rounded-full border border-input "
+ "has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50 "
+ "has-[:focus-visible]:border-ring has-[:focus-visible]:ring-3 has-[:focus-visible]:ring-ring/50 "
+ "has-[[aria-invalid=true]]:border-destructive has-[[aria-invalid=true]]:ring-3 "
+ "has-[[aria-invalid=true]]:ring-destructive/20 "
+ "dark:bg-input/30 "
+ "has-[:checked]:border-primary "
+ "peer-has-[[data-slot=field-content]]:mt-0.5 [&:has(~[data-slot=field-content])]:mt-0.5"
+ )
+
+ ITEM_INPUT = "peer sr-only"
+
+ ITEM_INDICATOR = "hidden peer-checked:flex items-center justify-center"
+
+ ITEM_INDICATOR_DOT = "size-2 rounded-full bg-primary"
+
+ _KNOWN_INPUT_PROPS = (
+ "checked",
+ "default_checked",
+ "disabled",
+ "required",
+ "id",
+ )
+
+
+class RadioGroupRoot(CoreComponent):
+ @classmethod
+ def create(cls, *children, disabled: bool = False, **props) -> rx.Component:
+ custom_classes = props.pop("class_name", "")
+ props["data-slot"] = "radio-group"
+ props["role"] = "radiogroup"
+ if disabled:
+ props["disabled"] = True
+ cls.set_class_name(cn(ClassNames.ROOT, custom_classes), props)
+ return rx.el.fieldset(*children, **props)
+
+
+class RadioGroupItem(CoreComponent):
+ @classmethod
+ def create(cls, *children, name: str, value: str, **props) -> rx.Component:
+ custom_classes = props.pop("class_name", "")
+
+ input_props = {"name": name, "value": value}
+ for key in list(props.keys()):
+ if (
+ key in ClassNames._KNOWN_INPUT_PROPS
+ or key.startswith("on_")
+ or key.startswith("data-")
+ or key.startswith("aria-")
+ ):
+ input_props[key] = props.pop(key)
+
+ input_props["type"] = "radio"
+ input_props["data-slot"] = "radio-group-item-input"
+ input_props["class_name"] = ClassNames.ITEM_INPUT
+
+ props["data-slot"] = "radio-group-item"
+
+ if not children:
+ children = (RadioGroupIndicator.create(),)
+
+ cls.set_class_name(cn(ClassNames.ITEM_ROOT, custom_classes), props)
+ return rx.el.label(rx.el.input(**input_props), *children, **props)
+
+
+class RadioGroupIndicator(CoreComponent):
+ @classmethod
+ def create(cls, *children, **props) -> rx.Component:
+ if len(children) == 0:
+ children = (rx.el.span(class_name=ClassNames.ITEM_INDICATOR_DOT),)
+ props["data-slot"] = "radio-group-item-indicator"
+ cls.set_class_name(ClassNames.ITEM_INDICATOR, props)
+ return rx.el.span(*children, **props)
+
+
+class RadioGroup(ComponentNamespace):
+ root = staticmethod(RadioGroupRoot.create)
+ item = staticmethod(RadioGroupItem.create)
+ indicator = staticmethod(RadioGroupIndicator.create)
+ class_names = ClassNames
+
+
+radio_group = RadioGroup()
diff --git a/components/ui/switch.py b/components/ui/switch.py
index 68dca94..9f367a0 100644
--- a/components/ui/switch.py
+++ b/components/ui/switch.py
@@ -68,11 +68,7 @@ def create(cls, label_text: str = "", **props) -> rx.Component:
return rx.el.label(
rx.el.input(type="checkbox", class_name=ClassNames.INPUT, **input_props),
rx.el.div(
- rx.el.span(
- # Dynamically merge custom thumb classes
- class_name=cn(ClassNames.THUMB, thumb_class_name)
- ),
- # Dynamically merge custom track classes
+ rx.el.span(class_name=cn(ClassNames.THUMB, thumb_class_name)),
class_name=cn(ClassNames.TRACK, track_class_name),
),
rx.el.span(label_text, class_name=ClassNames.LABEL)
diff --git a/components/ui/table.py b/components/ui/table.py
index 7373255..f7c3d13 100644
--- a/components/ui/table.py
+++ b/components/ui/table.py
@@ -77,26 +77,6 @@ def _sort_icons() -> list[Component]:
]
-# --------------------------------------------------------------------------
-# Client-side controller. Returned from `add_custom_code`, so Reflex
-# de-duplicates it and injects it once per page no matter how many
-# instances exist — same idea as the per-instance scripts
-# in dialog.py, just shared instead of inlined per node.
-#
-# Opt-in hooks (all via data-* attributes, none required):
-# [data-dt-search-for="tableId"] search input, anywhere on the page —
-# see TableSearch / table.search()
-# [data-dt-sort="key"] sortable header button (paired with a
-# `data-sort-value` attr on the matching
-# | , or falls back to that cell's text)
-# [data-dt-prev] / [data-dt-next] / [data-dt-info] / [data-dt-page-label]
-# pagination controls
-# [data-dt-select-all] header "select all" checkbox
-# [data-dt-select] per-row checkbox — must have a
-# unique `value` (e.g. a row id)
-# --------------------------------------------------------------------------
-
-
DATA_TABLE_JS = r"""
if (typeof window !== "undefined") {
window.__dataTable = window.__dataTable || {
diff --git a/docs/components/field.md b/docs/components/field.md
index 7ec0fc6..3f37f48 100644
--- a/docs/components/field.md
+++ b/docs/components/field.md
@@ -16,51 +16,50 @@ order: 0
A single control with label, helper text, and validation.
-```python
+```text
field.root
-├── field.label
-├── Input / Textarea / switch.root / select.root
-├── field.description
-└── field.error
+ |- field.label
+ |- Input / Textarea / switch.root / select.root
+ |- field.description
+ \- field.error
```
## field.group
Related fields in one group. Use `field.separator` between sections when needed.
-```python
+```text
field.group
-├── field.root
-│ ├── field.label
-│ ├── Input / Textarea / switch.root / select.root
-│ ├── field.description
-│ └── field.error
-├── field.separator
-└── field.root
- ├── field.label
- └── Input / Textarea / switch.root / select.root
+ |- field.root
+ | |- field.label
+ | |- Input / Textarea / switch.root / select.root
+ | |- field.description
+ | \- field.error
+ |- field.separator
+ \- field.root
+ |- field.label
+ \- Input / Textarea / switch.root / select.root
```
## field.set
Semantic grouping with a legend and description, usually containing a `field.group`.
-```python
+```text
field.set
-├── field.legend
-├── field.description
-└── field.group
- ├── field.root
- │ ├── field.label
- │ ├── Input / Textarea / switch.root / select.root
- │ ├── field.description
- │ └── field.error
- └── field.root
- ├── field.label
- └── Input / Textarea / switch.root / select.root
+ |- field.legend
+ |- field.description
+ \- field.group
+ |- field.root
+ | |- field.label
+ | |- Input / Textarea / switch.root / select.root
+ | |- field.description
+ | \- field.error
+ \- field.root
+ |- field.label
+ \- Input / Textarea / switch.root / select.root
```
-
- `field.root` is the core wrapper for a single field.
- `field.content` is a flex column that groups label and description. Not required if you have no description.
@@ -79,9 +78,9 @@ Demonstrates `field.root`, `field.group`, `field.set`, `field.label`, `field.des
# Responsive Layout
-- **Vertical fields**: Default orientation stacks label, control, and helper text—ideal for mobile-first layouts.
+- **Vertical fields**: Default orientation stacks label, control, and helper text—ideal for mobile-first layouts.
-- **Horizontal fields**: Set `orientation="horizontal"` on `field.root` to align the label and control side-by-side. Pair with `field.content` to keep descriptions aligned.
+- **Horizontal fields**: Set `orientation="horizontal"` on `field.root` to align the label and control side-by-side. Pair with `field.content` to keep descriptions aligned.
- **Responsive fields**: Set `orientation="responsive"` for automatic column layouts inside container-aware parents.
@@ -108,9 +107,9 @@ field.root(
Container that renders a semantic `fieldset` with spacing presets.
-| Prop | Type | Default |
-| --- | --- | --- |
-| `class_name` | `str` | |
+| Prop | Type | Default |
+| ------------ | ----- | ------- |
+| `class_name` | `str` | |
```python
field.set(
@@ -123,10 +122,10 @@ field.set(
Legend element for a `field.set`. Switch to the `"label"` variant to align with standard label sizing.
-| Prop | Type | Default |
-| --- | --- | --- |
-| `variant` | `Literal["legend", "label"]` | `"legend"` |
-| `class_name` | `str` | |
+| Prop | Type | Default |
+| ------------ | ---------------------------- | ---------- |
+| `variant` | `Literal["legend", "label"]` | `"legend"` |
+| `class_name` | `str` | |
```python
field.legend("Notification Preferences", variant="label")
@@ -138,9 +137,9 @@ The `field.legend` has two variants: `legend` and `label`. The `label` variant a
Layout wrapper that stacks `field.root` components and enables container queries for responsive orientations.
-| Prop | Type | Default |
-| --- | --- | --- |
-| `class_name` | `str` | |
+| Prop | Type | Default |
+| ------------ | ----- | ------- |
+| `class_name` | `str` | |
```python
field.group(
@@ -154,11 +153,11 @@ field.group(
The core wrapper for a single field. Provides orientation control, invalid state styling, and spacing configurations.
-| Prop | Type | Default |
-| --- | --- | --- |
-| `orientation` | `"vertical" | "horizontal" | "responsive"` | `"vertical"` |
-| `class_name` | `str` | |
-| `data_invalid` | `str` | |
+| Prop | Type | Default |
+| -------------- | ----------- | ------------ | ------------- | ------------ |
+| `orientation` | `"vertical" | "horizontal" | "responsive"` | `"vertical"` |
+| `class_name` | `str` | |
+| `data_invalid` | `str` | |
```python
field.root(
@@ -172,9 +171,9 @@ field.root(
Flex column that groups control and descriptions when the label sits beside the control. Not required if you have no layout description block.
-| Prop | Type | Default |
-| --- | --- | --- |
-| `class_name` | `str` | |
+| Prop | Type | Default |
+| ------------ | ----- | ------- |
+| `class_name` | `str` | |
```python
field.root(
@@ -190,10 +189,10 @@ field.root(
Label styled for both direct inputs and nested `field` child items.
-| Prop | Type | Default |
-| --- | --- | --- |
-| `html_for` | `str` | |
-| `class_name` | `str` | |
+| Prop | Type | Default |
+| ------------ | ----- | ------- |
+| `html_for` | `str` | |
+| `class_name` | `str` | |
```python
field.label("Email", html_for="email")
@@ -203,9 +202,9 @@ field.label("Email", html_for="email")
Renders a standalone title with matching label typography properties inside a `field.content` node block.
-| Prop | Type | Default |
-| --- | --- | --- |
-| `class_name` | `str` | |
+| Prop | Type | Default |
+| ------------ | ----- | ------- |
+| `class_name` | `str` | |
```python
field.content(
@@ -218,9 +217,9 @@ field.content(
Helper text slot that automatically line-balances lengthy strings cleanly when utilized inside horizontal configurations.
-| Prop | Type | Default |
-| --- | --- | --- |
-| `class_name` | `str` | |
+| Prop | Type | Default |
+| ------------ | ----- | ------- |
+| `class_name` | `str` | |
```python
field.description("We never share your email with anyone.")
@@ -230,9 +229,9 @@ field.description("We never share your email with anyone.")
Visual divider rule used to separate sections or categories inside a wrapping `field.group` component. Accepts optional inline children contents.
-| Prop | Type | Default |
-| --- | --- | --- |
-| `class_name` | `str` | |
+| Prop | Type | Default |
+| ------------ | ----- | ------- |
+| `class_name` | `str` | |
```python
field.separator("Or continue with")
@@ -242,9 +241,9 @@ field.separator("Or continue with")
Accessible error notification typography container block configured automatically with standard application state layout variables (`role="alert"`).
-| Prop | Type | Default |
-| --- | --- | --- |
-| `class_name` | `str` | |
+| Prop | Type | Default |
+| ------------ | ----- | ------- |
+| `class_name` | `str` | |
```python
field.error("Invalid passcode combination provided.")
diff --git a/docs/components/radio_group.md b/docs/components/radio_group.md
new file mode 100644
index 0000000..2c10e7c
--- /dev/null
+++ b/docs/components/radio_group.md
@@ -0,0 +1,110 @@
+---
+title: "Radio Group"
+description: "A set of checkable buttons—known as radio buttons—where no more than one of the buttons can be checked at a time."
+order: 0
+---
+
+--INTRO([Radio Group, A set of checkable buttons—known as radio buttons—where no more than one of the buttons can be checked at a time.])--
+
+--USAGE(radio_group)--
+
+--SOURCE(radio_group)--
+
+# Examples
+
+## Description
+
+Radio group items with a description using the `Field` component.
+
+--DEMO(radio_group_description)--
+
+## Choice Card
+
+Use `field.label` to wrap the entire `Field` for a clickable card-style selection.
+
+--DEMO(radio_group_choice_card)--
+
+## Fieldset
+
+Use `field.set` and `field.legend` to group radio items with a label and description.
+
+--DEMO(radio_group_fieldset)--
+
+## Disabled
+
+Use the `disabled` prop on `radio.root` to disable all items.
+
+--DEMO(radio_group_disabled)--
+
+## Invalid
+
+Use `aria-invalid` on `radio.item` and `data-invalid` on `field.root` to show validation errors.
+
+--DEMO(radio_group_invalid)--
+
+# API Reference
+
+## radio_group.root
+
+The container element for a set of radio options. Renders a native ` | |