HextaUI

Radio group

Wähle eine Option aus einer Menge, mit einem Punkt, der zwischen den Optionen übergeben wird, und Karten, deren Auswahlring zur neuen Wahl gleitet.

import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"

const options = [
  { value: "all", label: "All new messages" },
  { value: "mentions", label: "Direct messages and mentions" },
  { value: "none", label: "Nothing" },
]

export function RadioGroupDemo() {
  return (
    <RadioGroup
      aria-label="Notify me about"
      defaultValue="mentions"
      className="w-fit"
    >
      {options.map((option) => (
        <label key={option.value} className="flex items-center gap-3 text-sm">
          <RadioGroupItem value={option.value} />
          {option.label}
        </label>
      ))}
    </RadioGroup>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/radio-group.json

Fügt die Komponente, die HextaUI-Theme-Tokens und alle HextaUI-Komponenten hinzu, von denen sie abhängt.

import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
<RadioGroup aria-label="Notify me about" defaultValue="mentions">
  <label className="flex items-center gap-3">
    <RadioGroupItem value="all" />
    All new messages
  </label>
  <label className="flex items-center gap-3">
    <RadioGroupItem value="mentions" />
    Direct messages and mentions
  </label>
</RadioGroup>

Ändert sich die Auswahl, schrumpft der alte Punkt weg, während der neue hereinfedert, sodass das Auge der Wahl folgt, statt dass zwei Dinge gleichzeitig blinken. Umschließe jedes Item mit einem <label>, damit die ganze Zeile klickbar ist.

RadioGroup
├── RadioGroupItem
└── RadioGroupCard
    ├── RadioGroupCardTitle
    └── RadioGroupCardDescription

Karten

Mit variant="card" nutze RadioGroupCard für Optionen mit mehr zu sagen. Der Auswahlring gleitet zur neuen Karte, während ihre Fläche einblendet.

import {
  RadioGroup,
  RadioGroupCard,
  RadioGroupCardDescription,
  RadioGroupCardTitle,
} from "@/components/ui/radio-group"

const plans = [
  {
    value: "hobby",
    title: "Hobby",
    price: "Free",
    description: "For side projects and trying things out.",
  },
  {
    value: "pro",
    title: "Pro",
    price: "$20/mo",
    description: "Unlimited projects, analytics and priority builds.",
  },
  {
    value: "team",
    title: "Team",
    price: "$60/mo",
    description: "Everything in Pro, plus roles and audit logs.",
  },
]

export function RadioGroupCards() {
  return (
    <RadioGroup
      variant="card"
      aria-label="Plan"
      defaultValue="pro"
      className="max-w-md"
    >
      {plans.map((plan) => (
        <RadioGroupCard key={plan.value} value={plan.value}>
          <span className="flex items-baseline justify-between gap-3">
            <RadioGroupCardTitle>{plan.title}</RadioGroupCardTitle>
            <span className="text-muted-foreground tabular-nums">
              {plan.price}
            </span>
          </span>
          <RadioGroupCardDescription>
            {plan.description}
          </RadioGroupCardDescription>
        </RadioGroupCard>
      ))}
    </RadioGroup>
  )
}

Horizontal

Karten können in einer Zeile oder einem beliebigen Raster stehen. Der Ring folgt ihnen, wo immer sie sind.

import {
  RadioGroup,
  RadioGroupCard,
  RadioGroupCardTitle,
} from "@/components/ui/radio-group"

const sizes = ["S", "M", "L", "XL"]

export function RadioGroupHorizontal() {
  return (
    <RadioGroup
      variant="card"
      aria-label="Size"
      defaultValue="M"
      className="w-full max-w-sm grid-cols-2 sm:grid-cols-4"
    >
      {sizes.map((size) => (
        <RadioGroupCard key={size} value={size}>
          <RadioGroupCardTitle>{size}</RadioGroupCardTitle>
        </RadioGroupCard>
      ))}
    </RadioGroup>
  )
}

Form

In Field und FieldSet erhält die Gruppe eine Legende, Pflichtvalidierung und eine Fehlermeldung.

"use client"

import * as React from "react"
import { Form } from "@base-ui/react/form"

import { Button } from "@/components/ui/button"
import {
  Field,
  FieldError,
  FieldItem,
  FieldLabel,
  FieldLegend,
  FieldSet,
} from "@/components/ui/field"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"

export function RadioGroupForm() {
  const [submitted, setSubmitted] = React.useState<string>()

  return (
    <Form
      className="flex w-full max-w-xs flex-col items-start gap-4"
      onSubmit={(event) => {
        event.preventDefault()
        setSubmitted(String(new FormData(event.currentTarget).get("shipping")))
      }}
    >
      <Field name="shipping">
        <FieldSet render={<RadioGroup required />}>
          <FieldLegend variant="label">Shipping</FieldLegend>
          {["Standard", "Express", "Overnight"].map((option) => (
            <FieldItem key={option}>
              <RadioGroupItem value={option.toLowerCase()} />
              <FieldLabel>{option}</FieldLabel>
            </FieldItem>
          ))}
        </FieldSet>
        <FieldError match="valueMissing">Choose a shipping speed.</FieldError>
      </Field>
      <Button type="submit" size="sm">
        Continue
      </Button>
      <output className="text-sm text-muted-foreground">
        {submitted ? `shipping=${submitted}` : "Nothing submitted yet."}
      </output>
    </Form>
  )
}

Deaktiviert und schreibgeschützt

Deaktiviere eine einzelne Option oder setze die ganze Gruppe auf readOnly, um einen unveränderlichen Wert zu zeigen.

import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"

export function RadioGroupStates() {
  return (
    <div className="flex flex-wrap gap-12 text-sm">
      <RadioGroup aria-label="Theme" defaultValue="system" className="w-fit">
        <label className="flex items-center gap-3">
          <RadioGroupItem value="light" />
          Light
        </label>
        <label className="flex items-center gap-3">
          <RadioGroupItem value="dark" disabled />
          Dark (coming soon)
        </label>
        <label className="flex items-center gap-3">
          <RadioGroupItem value="system" />
          System
        </label>
      </RadioGroup>
      <RadioGroup
        aria-label="Region"
        defaultValue="eu"
        readOnly
        className="w-fit"
      >
        <label className="flex items-center gap-3">
          <RadioGroupItem value="us" />
          United States
        </label>
        <label className="flex items-center gap-3">
          <RadioGroupItem value="eu" />
          Europe (locked)
        </label>
      </RadioGroup>
    </div>
  )
}

Kontrolliert

Übergib value und onValueChange. Änderungen von außen werden genauso animiert wie Klicks.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"

const speeds = ["0.5×", "1×", "1.5×", "2×"]

export function RadioGroupControlled() {
  const [speed, setSpeed] = React.useState("1×")

  return (
    <div className="flex flex-col items-start gap-4 text-sm">
      <RadioGroup
        aria-label="Playback speed"
        value={speed}
        onValueChange={setSpeed}
        className="flex w-fit flex-wrap"
      >
        {speeds.map((value) => (
          <label key={value} className="flex items-center gap-2 tabular-nums">
            <RadioGroupItem value={value} />
            {value}
          </label>
        ))}
      </RadioGroup>
      <Button
        variant="outline"
        size="sm"
        onClick={() =>
          setSpeed(speeds[(speeds.indexOf(speed) + 1) % speeds.length])
        }
      >
        Next speed
      </Button>
    </div>
  )
}

Rechts nach links

Radios und Karten folgen der Leserichtung.

import {
  RadioGroup,
  RadioGroupCard,
  RadioGroupCardDescription,
  RadioGroupCardTitle,
} from "@/components/ui/radio-group"

export function RadioGroupRtl() {
  return (
    <div dir="rtl" className="w-full max-w-sm">
      <RadioGroup variant="card" aria-label="طريقة الدفع" defaultValue="card">
        <RadioGroupCard value="card">
          <RadioGroupCardTitle>بطاقة ائتمان</RadioGroupCardTitle>
          <RadioGroupCardDescription>
            فيزا أو ماستركارد.
          </RadioGroupCardDescription>
        </RadioGroupCard>
        <RadioGroupCard value="cash">
          <RadioGroupCardTitle>الدفع عند الاستلام</RadioGroupCardTitle>
          <RadioGroupCardDescription>
            ادفع نقدًا عند التوصيل.
          </RadioGroupCardDescription>
        </RadioGroupCard>
      </RadioGroup>
    </div>
  )
}
TasteAktion
TabSetzt den Fokus in die Gruppe, auf die gewählte oder die erste Option.
↑↓←→Wechselt zur vorherigen oder nächsten Option und wählt sie, an den Enden umlaufend.
SpaceWählt die fokussierte Option.
  • Benenne die Gruppe mit aria-label, aria-labelledby oder einer FieldLegend.
  • Jedes Radio hat eine 36px große Trefferfläche, auf Touchscreens 44px, bleibt aber auf dem Bildschirm 16px groß.
  • Punkt und Kartenring bewegen sich bei reduzierter Bewegung nicht.
PropTypStandard
variantcard fügt den gleitenden Auswahlring hinzu.
"default" | "card""default"
value
Value–
defaultValue
Value–
onValueChange
(value, details) => void–
name
string–
required
booleanfalse
disabled
booleanfalse
readOnly
booleanfalse
render
ReactElement | (props, state) => ReactElement<div>
AttributBeschreibung
data-slot="radio-group"Die Gruppe, mit data-variant.
data-slot="radio-group-highlight"Der Ring, der zwischen Karten gleitet.
data-disabledDie Gruppe ist deaktiviert.
PropTypStandard
value
Value–
disabled
booleanfalse
render
ReactElement | (props, state) => ReactElement<span>
AttributBeschreibung
data-slot="radio-group-item"Das Radio.
data-checked / data-uncheckedOb es die gewählte Option ist.
data-disabled / data-readonlyVorhanden, wenn es nicht geändert werden kann.
PropTypStandard
value
Value–
disabled
booleanfalse
radioClassNameKlassen für das Radio in der Karte.
string–

Ein <label> mit eingebautem Radio. Setze RadioGroupCardTitle und RadioGroupCardDescription oder beliebigen anderen Inhalt hinein.