A set of checkable buttons—known as radio buttons—where no more than one of the buttons can be checked at a time.
Pair with Field.Set and Field.Legend for grouped label/description/error wiring in forms. RadioGroup keeps its own name prop on RadioGroup.Root:
1import { Field } from "@ngrok/mantle/field";2import { RadioGroup } from "@ngrok/mantle/radio-group";3 4<Field.Set>5 <Field.Legend>Density</Field.Legend>6 <RadioGroup.Root name="density" defaultValue="compact">7 <RadioGroup.Item value="default" id="density-default">8 <RadioGroup.Indicator />9 <RadioGroup.ItemContent asChild>10 <label htmlFor="density-default">Default</label>11 </RadioGroup.ItemContent>12 </RadioGroup.Item>13 <RadioGroup.Item value="comfortable" id="density-comfortable">14 <RadioGroup.Indicator />15 <RadioGroup.ItemContent asChild>16 <label htmlFor="density-comfortable">Comfortable</label>17 </RadioGroup.ItemContent>18 </RadioGroup.Item>19 <RadioGroup.Item value="compact" id="density-compact">20 <RadioGroup.Indicator />21 <RadioGroup.ItemContent asChild>22 <label htmlFor="density-compact">Compact</label>23 </RadioGroup.ItemContent>24 </RadioGroup.Item>25 </RadioGroup.Root>26 <Field.Description>Choose the row spacing for tables.</Field.Description>27</Field.Set>;Or render the control on its own:
Laborum esse cillum incididunt est dolore.
Ea laboris tempor laborum officia ea adipisicing exercitation.
Adipisicing est dolore velit magna dolor voluptate velit.
Tempor dolore Lorem exercitation id nisi aliquip elit.
Only new workspace members are required to use SSO. Existing members can still log in with other methods.
All workspace members are required to log in with SSO.
Last message sent an hour ago
621 users
Last message sent 2 weeks ago
1200 users
Last message sent 4 days ago
2740 Users
1import { RadioGroup } from "@ngrok/mantle/radio-group";2 3<RadioGroup.Root defaultValue="comfortable">4 <RadioGroup.Item className="py-1" value="default" id="simple-1">5 <RadioGroup.Indicator />6 <RadioGroup.ItemContent asChild>7 <label htmlFor="simple-1">Default</label>8 </RadioGroup.ItemContent>9 </RadioGroup.Item>10 <RadioGroup.Item className="py-1" value="comfortable" id="simple-2" disabled>11 <RadioGroup.Indicator />12 <RadioGroup.ItemContent asChild>13 <label htmlFor="simple-2">Comfortable</label>14 </RadioGroup.ItemContent>15 </RadioGroup.Item>16 <RadioGroup.Item className="py-1" value="compact" id="simple-3">17 <RadioGroup.Indicator />18 <RadioGroup.ItemContent asChild>19 <label htmlFor="simple-3">Compact</label>20 </RadioGroup.ItemContent>21 </RadioGroup.Item>22</RadioGroup.Root>23 24<RadioGroup.ButtonGroup defaultValue="production">25 <RadioGroup.Button value="development">Development</RadioGroup.Button>26 <RadioGroup.Button value="staging">Staging</RadioGroup.Button>27 <RadioGroup.Button value="production">Production</RadioGroup.Button>28</RadioGroup.ButtonGroup>29 30<RadioGroup.List defaultValue="comfortable">31 <RadioGroup.ListItem value="default" disabled id="rli1">32 <RadioGroup.Indicator />33 <RadioGroup.ItemContent>34 <label className="font-medium text-strong" htmlFor="rli1">Default</label>35 <p className="text-body">Laborum esse cillum incididunt est dolore.</p>36 </RadioGroup.ItemContent>37 </RadioGroup.ListItem>38 <RadioGroup.ListItem value="comfortable" id="rli2">39 <RadioGroup.Indicator />40 <RadioGroup.ItemContent>41 <label className="font-medium text-strong" htmlFor="rli2">Comfortable</label>42 <p className="text-body">Ea laboris tempor laborum officia ea adipisicing exercitation.</p>43 </RadioGroup.ItemContent>44 </RadioGroup.ListItem>45</RadioGroup.List>46 47<RadioGroup.Root className="grid grid-cols-1 gap-y-6 sm:grid-cols-3 sm:gap-x-4" defaultValue="existing">48 <RadioGroup.Card className="flex" value="newsletter" id="radiocard-1">49 <div className="flex-1">50 <label htmlFor="radiocard-1" className="block text-sm font-medium text-strong">Newsletter</label>51 <p className="mt-1 flex items-center text-sm text-gray-500">Last message sent an hour ago</p>52 <p className="mt-6 text-sm font-medium">621 users</p>53 </div>54 <RadioGroup.Indicator />55 </RadioGroup.Card>56</RadioGroup.Root>Use @tanstack/react-form with zod to keep a radio group controlled. Use Field.Set and Field.Legend because the grouped choices answer one shared question.
1import { Button } from "@ngrok/mantle/button";2import { Field, toErrorMessages } from "@ngrok/mantle/field";3import { RadioGroup } from "@ngrok/mantle/radio-group";4import { useForm } from "@tanstack/react-form";5import { z } from "zod";6 7export const formSchema = z.object({8 density: z.string().min(1, "Choose a density."),9});10function Example() {11 const defaultValues = {12 density: "",13 };14 const form = useForm({15 defaultValues,16 validators: {17 onSubmit: formSchema,18 },19 onSubmit: ({ value }) => {20 // Handle form submission here21 },22 });23 24 return (25 <form26 onSubmit={(event) => {27 event.preventDefault();28 event.stopPropagation();29 void form.handleSubmit();30 }}31 >32 <form.Field name="density">33 {(field) => (34 <Field.Set>35 <Field.Legend>Density</Field.Legend>36 <RadioGroup.Root37 name={field.name}38 value={field.state.value}39 onChange={field.handleChange}40 >41 <RadioGroup.Item value="default" id="density-default">42 <RadioGroup.Indicator />43 <RadioGroup.ItemContent asChild>44 <label htmlFor="density-default">Default</label>45 </RadioGroup.ItemContent>46 </RadioGroup.Item>47 <RadioGroup.Item value="comfortable" id="density-comfortable">48 <RadioGroup.Indicator />49 <RadioGroup.ItemContent asChild>50 <label htmlFor="density-comfortable">Comfortable</label>51 </RadioGroup.ItemContent>52 </RadioGroup.Item>53 <RadioGroup.Item value="compact" id="density-compact">54 <RadioGroup.Indicator />55 <RadioGroup.ItemContent asChild>56 <label htmlFor="density-compact">Compact</label>57 </RadioGroup.ItemContent>58 </RadioGroup.Item>59 </RadioGroup.Root>60 <Field.Errors messages={toErrorMessages(field.state.meta.errors)} />61 </Field.Set>62 )}63 </form.Field>64 <Button type="submit" appearance="filled">65 Submit66 </Button>67 </form>68 );69}RadioGroup supports several layout variants. Compose the parts together to build your own:
# Default radiosRadioGroup.Root└── RadioGroup.Item ├── RadioGroup.Indicator └── RadioGroup.ItemContent# List layout with descriptionsRadioGroup.List└── RadioGroup.ListItem ├── RadioGroup.Indicator └── RadioGroup.ItemContent# Segmented button groupRadioGroup.ButtonGroup└── RadioGroup.Button# Card-style radiosRadioGroup.Root└── RadioGroup.Card └── RadioGroup.IndicatorA group of radio items. Manages state so only one item can be selected at a time. Unstyled and simple.
All props from Headless UI RadioGroup, including:
A simple radio item. The conventional use-case for basic radio options. Must be a child of RadioGroup.Root.
The selection indicator for any radio item. By default, renders a circle that changes color when checked. Use as a child of RadioGroup.Item, RadioGroup.ListItem, or RadioGroup.Card.
Accepts a render-props function (context) => ReactNode as children for custom indicators. The context includes checked, disabled, focus, hover, and autofocus.
The content wrapper for any radio item. Wrap labels, descriptions, or other content inside.
All props from div, plus:
An inline group of radio buttons rendered as a horizontal segmented control. Use RadioGroup.Button as direct children.
Same props as RadioGroup.Root.
A radio button used inside RadioGroup.ButtonGroup for inline grouped radio options.
A group of radio list items with connected borders. Use RadioGroup.ListItem as direct children.
Same props as RadioGroup.Root.
A radio list item used inside RadioGroup.List for connected list-style radio options with borders and padding.
A radio card item with enhanced styling for card-based radio options.
A sandbox container for input elements composed within radio group items. Prevents default radio selection behavior when interacting with the input.