Theme
Version
GitHub PyPI Discord
On this page

Radio

Use CRadioGroup and CRadio when people should see every option and select exactly one. Native fieldset, legend, labels, keyboard behavior, validity, reset, and FormData stay browser-owned.

Radio at a glance

Radio at a glance
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class RadioAtAGlance(Component):
    template = """
      <section class="radio-glance">
        <h2>Plan the garden path</h2>
        <p>Choose the habitat the path should pass through.</p>
        <c-CRadioGroup name="habitat" value="woodland" orientation="horizontal">
          <c-fill name="label">Habitat</c-fill>
          <c-fill name="default">
            <c-CRadio value="woodland">Woodland</c-CRadio>
            <c-CRadio value="meadow">Wildflower meadow</c-CRadio>
            <c-CRadio value="wetland">Wetland edge</c-CRadio>
          </c-fill>
        </c-CRadioGroup>
      </section>
    """
    css = """
      :where(.radio-glance) {
        display: grid;
        gap: 0.85rem;
        max-inline-size: 42rem;
        padding: 1.25rem;
        border: 1px solid light-dark(#a6b99b, #51664a);
        border-radius: 0.9rem;
        background: light-dark(#f4f8ef, #182219);
        color: CanvasText;
        font-family: ui-sans-serif, system-ui, sans-serif;
      }

      :where(.radio-glance h2, .radio-glance p) {
        margin: 0;
      }

      :where(.radio-glance > p) {
        color: light-dark(#53634c, #b8c9b0);
        font-size: 0.82rem;
      }
    """


preview = RadioAtAGlance()

preview  # noqa: B018

Compose a group

Give Group one shared name, a visible label slot, and Radios with unique values. CRadio cannot be used outside Group.

Compose a Radio Group
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class BasicRadioGroup(Component):
    template = """
      <c-CRadioGroup name="watering" value="morning">
        <c-fill name="label">Watering time</c-fill>
        <c-fill name="default">
          <c-CRadio value="morning">Early morning</c-CRadio>
          <c-CRadio value="evening">Late evening</c-CRadio>
        </c-fill>
      </c-CRadioGroup>
    """


preview = BasicRadioGroup()

preview  # noqa: B018
<c-CRadioGroup name="habitat" value="woodland">
  <c-fill name="label">Habitat</c-fill>
  <c-fill name="default">
    <c-CRadio value="woodland">Woodland</c-CRadio>
    <c-CRadio value="wetland">Wetland</c-CRadio>
  </c-fill>
</c-CRadioGroup>

Add descriptions and disabled choices

Descriptions connect to their native Radio. Disable one unavailable option without disabling its siblings.

Describe and disable Radio options
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class DescribedRadios(Component):
    template = """
      <c-CRadioGroup name="soil" value="loam" class_="radio-described">
        <c-fill name="label">Soil blend</c-fill>
        <c-fill name="default">
          <c-CRadio value="loam">
            <c-fill name="default">Woodland loam</c-fill>
            <c-fill name="description">Balanced drainage for ferns and woodland flowers.</c-fill>
          </c-CRadio>
          <c-CRadio value="grit">
            <c-fill name="default">Alpine grit</c-fill>
            <c-fill name="description">Fast drainage for rock-garden plants.</c-fill>
          </c-CRadio>
          <c-CRadio value="peat" disabled>
            <c-fill name="default">Bog peat</c-fill>
            <c-fill name="description">Unavailable while the bog bed recovers.</c-fill>
          </c-CRadio>
        </c-fill>
      </c-CRadioGroup>
    """
    css = """
      :where(.radio-described) {
        max-inline-size: 34rem;
      }
    """


preview = DescribedRadios()

preview  # noqa: B018

Control selection in the browser

Pass value through $c-props="{...}". A known string controls one option; null clears selection; omission releases control. Handle native input or change with $event.target.value.

Control a Radio Group
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class ControlledRadios(Component):
    template = """
      <section class="radio-controlled" x-data="{value: 'moss'}">
        <c-CRadioGroup
          name="groundcover"
          $c-props="{value}"
          @input="value = $event.target.value"
          orientation="horizontal"
        >
          <c-fill name="label">Ground cover</c-fill>
          <c-fill name="default">
            <c-CRadio value="moss">Moss</c-CRadio>
            <c-CRadio value="thyme">Creeping thyme</c-CRadio>
            <c-CRadio value="clover">Microclover</c-CRadio>
          </c-fill>
        </c-CRadioGroup>
        <output x-text="`Selected: ${value}`"></output>
      </section>
    """
    css = """
      :where(.radio-controlled) {
        display: grid;
        gap: 0.75rem;
        color: CanvasText;
        font-family: ui-sans-serif, system-ui, sans-serif;
      }

      :where(.radio-controlled output) {
        color: light-dark(#3f6212, #bef264);
        font-size: 0.8rem;
      }
    """


preview = ControlledRadios()

preview  # noqa: B018

Use native forms and validation

The checked enabled Radio contributes one shared name/value entry. Required groups use native validation and reset.

Submit and validate Radio values
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class RadioForm(Component):
    template = """
      <form
        class="radio-form"
        x-data="{result: ''}"
        @submit.prevent="result = new FormData($event.target).get('plot') || 'Choose a plot'"
      >
        <c-CRadioGroup name="plot" required>
          <c-fill name="label">Planting plot</c-fill>
          <c-fill name="default">
            <c-CRadio value="north">North wall</c-CRadio>
            <c-CRadio value="orchard">Old orchard</c-CRadio>
            <c-CRadio value="pond">Pond margin</c-CRadio>
          </c-fill>
        </c-CRadioGroup>
        <c-CGroup><c-CButton type="submit">Reserve plot</c-CButton><button type="reset">Reset</button></c-CGroup>
        <output x-text="result"></output>
      </form>
    """
    css = """
      :where(.radio-form) {
        display: grid;
        gap: 1rem;
        max-inline-size: 34rem;
        color: CanvasText;
        font-family: ui-sans-serif, system-ui, sans-serif;
      }
    """


preview = RadioForm()

preview  # noqa: B018

Choose orientation

Vertical is easiest to scan. Horizontal groups wrap and keep native keyboard behavior.

Compare Radio orientations
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class RadioOrientation(Component):
    template = """
      <c-CStack gap="xl">
        <c-CRadioGroup name="season-vertical" value="spring">
          <c-fill name="label">Vertical</c-fill>
          <c-fill name="default">
            <c-CRadio value="spring">Spring</c-CRadio>
            <c-CRadio value="autumn">Autumn</c-CRadio>
          </c-fill>
        </c-CRadioGroup>
        <c-CRadioGroup name="season-horizontal" value="spring" orientation="horizontal">
          <c-fill name="label">Horizontal</c-fill>
          <c-fill name="default">
            <c-CRadio value="spring">Spring</c-CRadio>
            <c-CRadio value="autumn">Autumn</c-CRadio>
          </c-fill>
        </c-CRadioGroup>
      </c-CStack>
    """


preview = RadioOrientation()

preview  # noqa: B018

Choose presentation

Compare solid and outline treatments, three sizes, and logical label placement.

Compare Radio presentation
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class RadioPresentation(Component):
    class Kwargs:
        pass

    class Slots:
        pass

    template = """
      <c-CStack gap="xl">
        <c-for each="variant in variants">
          <c-CRadioGroup c-name="f'variant-{variant}'" value="one" c-variant="variant" orientation="horizontal">
            <c-fill name="label">{{ variant }}</c-fill>
            <c-fill name="default"><c-CRadio value="one">One</c-CRadio><c-CRadio value="two">Two</c-CRadio></c-fill>
          </c-CRadioGroup>
        </c-for>
        <c-for each="size in sizes">
          <c-CRadioGroup c-name="f'size-{size}'" value="leaf" c-size="size" label_pos="start" orientation="horizontal">
            <c-fill name="label">{{ size }}, labels first</c-fill>
            <c-fill name="default">
              <c-CRadio value="leaf">Leaf</c-CRadio>
              <c-CRadio value="flower">Flower</c-CRadio>
            </c-fill>
          </c-CRadioGroup>
        </c-for>
      </c-CStack>
    """

    def template_data(
        self,
        kwargs: Kwargs,  # noqa: ARG002
        slots: Slots,  # noqa: ARG002
    ) -> dict[str, object]:
        return {"variants": ("solid", "outline"), "sizes": ("sm", "md", "lg")}


preview = RadioPresentation()

preview  # noqa: B018

Compose with Field

Inside CField, Field owns label, description, error, required, disabled, and invalid state. Do not add the Group label slot there.

Compose Radio with Field
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class RadioField(Component):
    template = """
      <c-CField control_id="shade-choice" required>
        <c-fill name="label">Preferred shade</c-fill>
        <c-fill name="default">
          <c-CRadioGroup name="shade" orientation="horizontal">
            <c-CRadio value="sun">Full sun</c-CRadio>
            <c-CRadio value="partial">Partial shade</c-CRadio>
            <c-CRadio value="deep">Deep shade</c-CRadio>
          </c-CRadioGroup>
        </c-fill>
        <c-fill name="description">Choose the light available in this bed.</c-fill>
        <c-fill name="error">Choose one shade level.</c-fill>
      </c-CField>
    """


preview = RadioField()

preview  # noqa: B018

Customize Radio

Override public group, control, color, focus, spacing, and disabled variables. Stable part selectors target the fieldset, legend, item, input, label, and description.

Customize Radio with public CSS
Show code
import citry_ui
from citry import Component, citry

citry.register_library(citry_ui)


class RadioCustomization(Component):
    template = """
      <div class="radio-custom">
        <c-CRadioGroup name="collection" value="fern" orientation="horizontal">
          <c-fill name="label">Plant collection</c-fill>
          <c-fill name="default">
            <c-CRadio value="fern">Fern house</c-CRadio>
            <c-CRadio value="alpine">Alpine house</c-CRadio>
            <c-CRadio value="orchid">Orchid house</c-CRadio>
          </c-fill>
        </c-CRadioGroup>
      </div>
    """
    css = """
      :where(.radio-custom) {
        --cui-radio-active-color: light-dark(#7c3f00, #fbbf24);
        --cui-radio-border-color: light-dark(#a16207, #fde68a);
        --cui-radio-background: light-dark(#fffbeb, #2d2108);
        --cui-radio-control-size: 1.35rem;
        --cui-radio-group-gap: 1.25rem;
        padding: 1.25rem;
        border-radius: 0.8rem;
        background: light-dark(#f7f2df, #211d10);
      }
    """


preview = RadioCustomization()

preview  # noqa: B018

Choose the right control

Use Native Select when choices should collapse, Checkbox for independent choices, and Switch for an immediate Boolean setting. Radio Card and Segmented Control are separate interaction and anatomy families.

API reference

Inputs

CRadioGroup server inputs

Server inputs are passed in a template through <c-CRadioGroup ... /> or in Python through CRadioGroup(...).

InputTypeDefaultEffect
namestrrequiredSets the required shared native radio-group and FormData name.
valuestr | NoneNoneSets initial checked value; it must match one Radio value; None leaves the group unselected.
formstr | NoneNoneAssociates every Radio with an external native Form ID.
requiredbool | NoneNoneEnables native same-name group validation; CField owns it when composed.
disabledbool | NoneNoneDisables the native fieldset; CField and CForm remain dominant.
invalidbool | NoneNoneSets explicit invalid styling and ARIA; CField owns it when composed.
orientation"vertical" | "horizontal" (CRadioOrientation)"vertical"Selects stacked or wrapping inline layout without replacing native keyboard behavior.
variant"solid" | "outline" (CRadioVariant)"solid"Selects checked-control treatment.
size"sm" | "md" | "lg" (CRadioSize)"md"Sets control and text scale.
label_pos"start" | "end" (CRadioLabelPos)"end"Places item labels before or after controls.
idstr | NoneNoneSets the fieldset ID.
class_str | Mapping[str, bool] | Sequence[CClassValue] | None (CClassValue)NoneAdds fieldset classes and merges them with attrs.
stylestr | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue] | None (CStyleValue)NoneAdds fieldset inline styles and merges them with attrs.
attrsMapping[str, object] | NoneNoneAdds copied trusted nonconflicting metadata and targeted Alpine attributes to the fieldset.

CRadioGroup client inputs

Client inputs are passed in the browser through the $c-props="{ ... }" attribute on <c-CRadioGroup />.

InputTypeOmitted behaviorEffect
valuestring | nullReleases control to native selection.Controls one known value or no selection; omission releases control.
requiredbooleanUses the server or Field fallback.Controls native required state outside Field.
disabledbooleanUses the server or Field/Form fallback.Controls local disabled state outside Field; Form disabled stays dominant.
invalidbooleanUses the server or Field fallback.Controls explicit invalid state outside Field.
orientation"vertical" | "horizontal"Uses the server fallback.Controls the public layout reflection.
variant"solid" | "outline"Uses the server fallback.Controls checked-control treatment.
size"sm" | "md" | "lg"Uses the server fallback.Controls public size.
label_pos"start" | "end"Uses the server fallback.Controls label placement.

CRadio server inputs

Server inputs are passed in a template through <c-CRadio ... /> or in Python through CRadio(...).

InputTypeDefaultEffect
valuestrrequiredSets unique canonical option and submitted value.
disabledboolFalseDisables this native Radio without disabling siblings.
class_str | Mapping[str, bool] | Sequence[CClassValue] | None (CClassValue)NoneAdds item wrapper classes and merges them with attrs.
stylestr | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue] | None (CStyleValue)NoneAdds item wrapper inline styles and merges them with attrs.
attrsMapping[str, object] | NoneNoneAdds copied trusted nonconflicting attributes to the item wrapper.
input_attrsMapping[str, object] | NoneNoneAdds copied trusted nonconflicting native metadata and event listeners to the Radio input.

Slots

Slots are passed as nested content or <c-fill> tags in a template, or through the slots={...} argument in Python.

CRadioGroup slots

SlotRequiredDataFallback
labelno{} (CRadioGroupLabelSlotData)Missing standalone label raises; the slot is forbidden under CField.
defaultyes{} (CRadioGroupDefaultSlotData)Missing fill raises before rendering.

CRadio slots

SlotRequiredDataFallback
defaultyes{} (CRadioDefaultSlotData)Missing visible label raises before rendering.
descriptionno{} (CRadioDescriptionSlotData)Description wrapper and relationship are omitted.

Events

-

Methods

-

CSS

CSS variables to theme the components. Set them on an ancestor or the component itself.

CRadioGroup CSS variables

Apply these variables to CRadioGroup or one of its ancestors.

VariableTypePurposeDefault
--cui-radio-group-gaplengthSpacing between items.0.75rem.
--cui-radio-active-colorcolorChecked border/fill/dot.Scheme-aware primary.
--cui-radio-border-colorcolorUnchecked border.Scheme-aware neutral.
--cui-radio-backgroundcolorNative control background.Canvas.
--cui-radio-foregroundcolorLabels and inherited text.CanvasText.
--cui-radio-focus-colorcolorKeyboard focus ring.Highlight.
--cui-radio-invalid-colorcolorInvalid control border.Scheme-aware danger.
--cui-radio-control-sizelengthRadio control box.Size-derived length.
--cui-radio-item-gaplengthControl-to-body spacing.0.55rem.
--cui-radio-label-gaplengthLabel-to-description spacing.0.2rem.
--cui-radio-disabled-opacitynumberDisabled item opacity.0.52.

Attributes

HTML attributes defined on the components that you can refer to for CSS, inspection, and testing. Read-only.

CRadioGroup attributes

AttributeElementTypeMeaning
disabledFieldsetboolean present or absentNative group disabled state.
aria-invalidFieldset"true" or absentEffective explicit or native invalid state.
data-valueFieldsetcanonical string or absentCurrent checked option value.
data-requiredFieldsetboolean present or absentEffective native-required request.
data-disabledFieldsetboolean present or absentEffective group disabled state.
data-invalidFieldsetboolean present or absentEffective invalid state.
data-orientationFieldset"vertical" | "horizontal"Effective layout.
data-variantFieldset"solid" | "outline"Effective selected-control treatment.
data-sizeFieldset"sm" | "md" | "lg"Effective size.
data-label-posFieldset"start" | "end"Effective label placement.

CRadio attributes

AttributeElementTypeMeaning
checkedNative inputboolean present or absentServer default checkedness; current checkedness is the native property.
disabledNative inputboolean present or absentItem-local disabledness.
nameNative inputnonempty stringShared Group name.
valueNative inputcanonical stringUnique option/FormData value.
data-checkedItem wrapperboolean present or absentMirrors current native checkedness for styling.
data-disabledItem wrapperboolean present or absentMirrors effective native disabledness.

Selectors

Selectors for the DOM nodes in the components that you can use for CSS, inspection, and testing.

CRadioGroup selectors

SelectorElementPurpose
[data-citry-ui-part="radio-group"]Native fieldsetGroup root and attrs destination.
[data-citry-ui-part="legend"]Native legendStandalone group label.
[data-citry-ui-part="radio"]Item wrapperRadio attrs destination.
[data-citry-ui-part="input"]Native radio inputInput attrs destination.
[data-citry-ui-part="body"]Item text wrapperLabel and description layout.
[data-citry-ui-part="label"]Native labelVisible option name and activation target.
[data-citry-ui-part="description"]Description spanOptional item guidance.

Interfaces

Aliases and data shapes referenced above.

Input type aliases

InterfaceDefinition
CClassValuestr | Mapping[str, bool] | Sequence[CClassValue]
CStyleValuestr | Mapping[str, str | int | float | bool | None] | Sequence[CStyleValue]
CRadioOrientationLiteral["vertical", "horizontal"]
CRadioVariantLiteral["solid", "outline"]
CRadioSizeLiteral["sm", "md", "lg"]
CRadioLabelPosLiteral["start", "end"]

CRadioGroupDefaultSlotData

Empty dataclass: {}.

CRadioGroupLabelSlotData

Empty dataclass: {}.

CRadioDefaultSlotData

Empty dataclass: {}.

CRadioDescriptionSlotData

Empty dataclass: {}.

Translation keys

-