Displays a list of options for the user to pick from—triggered by a button.
Use Select for a small, finite list of options (~2-15) where the user picks exactly one and search/filtering is unnecessary. For larger lists, async data, or any single-select where typing to filter is helpful, use Combobox. For picking multiple options, use Multi Select.
When a Select is rendered inside Field.Item, wrap Select.Root in Field.Control. Field.Control flows the generated id, name, aria-describedby, aria-errormessage, and aria-invalid through to the focusable Select.Trigger — the name lands on Select.Root (so the hidden form input picks it up) and the trigger reads the ARIA props from context.
1import { Field } from "@ngrok/mantle/field";2import { Select } from "@ngrok/mantle/select";3 4<Field.Item className="max-w-64" name="fruits">5 <Field.Label>Fruits</Field.Label>6 <Field.Control>7 <Select.Root>8 <Select.Trigger>9 <Select.Value placeholder="Select a fruit" />10 </Select.Trigger>11 <Select.Content width="trigger">12 <Select.Group>13 <Select.Label>Fruits</Select.Label>14 <Select.Item value="apple">Apple</Select.Item>15 <Select.Item value="banana">Banana</Select.Item>16 <Select.Item value="blueberry">Blueberry</Select.Item>17 <Select.Item value="grapes">Grapes</Select.Item>18 <Select.Item value="pineapple">Pineapple</Select.Item>19 </Select.Group>20 <Select.Separator />21 <Select.Group>22 <Select.Label>Vegetables</Select.Label>23 <Select.Item value="carrot">Carrot</Select.Item>24 <Select.Item value="cucumber">Cucumber</Select.Item>25 <Select.Item value="lettuce">Lettuce</Select.Item>26 <Select.Item value="tomato">Tomato</Select.Item>27 <Select.Item value="zucchini">28 <p>Zucchini</p>29 <p>Ex sit voluptate incididunt pariatur velit consequat reprehenderit.</p>30 </Select.Item>31 </Select.Group>32 </Select.Content>33 </Select.Root>34 </Field.Control>35</Field.Item>;Use @tanstack/react-form with zod to keep Select.Root controlled. Wrap the root in Field.Control so the generated name, id, and ARIA props reach the select trigger and hidden input.
1import { Button } from "@ngrok/mantle/button";2import { Field, toErrorMessages } from "@ngrok/mantle/field";3import { Select } from "@ngrok/mantle/select";4import { useForm } from "@tanstack/react-form";5import { z } from "zod";6 7export const formSchema = z.object({8 fruit: z.string().min(1, "Choose a fruit."),9});10function Example() {11 const defaultValues = {12 fruit: "",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="fruit">33 {(field) => (34 <Field.Item name={field.name}>35 <Field.Label>Fruit</Field.Label>36 <Field.Control>37 <Select.Root38 value={field.state.value}39 onBlur={field.handleBlur}40 onValueChange={field.handleChange}41 >42 <Select.Trigger>43 <Select.Value placeholder="Select a fruit" />44 </Select.Trigger>45 <Select.Content width="trigger">46 <Select.Item value="apple">Apple</Select.Item>47 <Select.Item value="banana">Banana</Select.Item>48 <Select.Item value="blueberry">Blueberry</Select.Item>49 </Select.Content>50 </Select.Root>51 </Field.Control>52 <Field.Errors messages={toErrorMessages(field.state.meta.errors)} />53 </Field.Item>54 )}55 </form.Field>56 <Button type="submit" appearance="filled">57 Submit58 </Button>59 </form>60 );61}Use Select.Root directly when you don't need Field label, helper, or error wiring. Set validation on Select.Root to render a success, warning, or error state outside of a field.
1import { Select } from "@ngrok/mantle/select";2 3<Select.Root>4 <Select.Trigger className="max-w-64" aria-label="Fruit">5 <Select.Value placeholder="Select a fruit" />6 </Select.Trigger>7 <Select.Content width="trigger">8 <Select.Item value="apple">Apple</Select.Item>9 <Select.Item value="banana">Banana</Select.Item>10 <Select.Item value="blueberry">Blueberry</Select.Item>11 </Select.Content>12</Select.Root>13 14<Select.Root validation="error">15 <Select.Trigger className="max-w-64" aria-label="Fruit with error">16 <Select.Value placeholder="Select a fruit" />17 </Select.Trigger>18 <Select.Content width="trigger">19 <Select.Item value="apple">Apple</Select.Item>20 <Select.Item value="banana">Banana</Select.Item>21 <Select.Item value="blueberry">Blueberry</Select.Item>22 </Select.Content>23</Select.Root>24 25<Select.Root validation="success">26 <Select.Trigger className="max-w-64" aria-label="Fruit with success">27 <Select.Value placeholder="Select a fruit" />28 </Select.Trigger>29 <Select.Content width="trigger">30 <Select.Item value="apple">Apple</Select.Item>31 <Select.Item value="banana">Banana</Select.Item>32 <Select.Item value="blueberry">Blueberry</Select.Item>33 </Select.Content>34</Select.Root>35 36<Select.Root validation="warning">37 <Select.Trigger className="max-w-64" aria-label="Fruit with warning">38 <Select.Value placeholder="Select a fruit" />39 </Select.Trigger>40 <Select.Content width="trigger">41 <Select.Item value="apple">Apple</Select.Item>42 <Select.Item value="banana">Banana</Select.Item>43 <Select.Item value="blueberry">Blueberry</Select.Item>44 </Select.Content>45</Select.Root>;By default the selected item's text will be rendered when selected. Sometimes you may need to render something different. You can control the select and pass children instead.
1import { Select } from "@ngrok/mantle/select";2 3<Select.Root value={value} onValueChange={setValue}>4 <Select.Trigger className="w-45">5 <Select.Value placeholder="Select a fruit">6 {value === "apple" ? <>🍎 Apple!</> : <>🍑 Peach!</>}7 </Select.Value>8 </Select.Trigger>9 <Select.Content width="trigger">10 <Select.Item value="apple">Apple</Select.Item>11 <Select.Item value="peach">Peach</Select.Item>12 </Select.Content>13</Select.Root>;Compose the parts of a Select together to build your own:
Select.Root├── Select.Trigger│ └── Select.Value└── Select.Content ├── Select.Group │ ├── Select.Label │ └── Select.Item └── Select.SeparatorThe Select components are built on top of Radix Select.
Displays a list of options for the user to pick from—triggered by a button.
Set validation on Select.Root when the whole select has an explicit state; this root-level state is forwarded to the trigger and takes precedence over ambient field-level validation from Field.Item.
Rendered Field.Errors or Field.ErrorList still force the trigger into the error state because Field.Control sets aria-invalid="true" on the focusable trigger, and an explicit invalid ARIA value always wins. If a non-error Select.Root state needs to win even when errors are rendered, pass validation on Field.Item to suppress the inferred error.
All props from Radix Select.Root, plus:
The button that toggles the Select. The Select.Content will position itself adjacent to the trigger.
When composing with Field.Item, wrap Select.Root (not the trigger) in Field.Control. The generated id, name, and aria-invalid flow onto Select.Root — so the hidden form input picks up the field name — and the trigger reads aria-describedby / aria-errormessage from FieldControlContext. Validation resolves from explicit Select.Root, then Select.Trigger, then the nearest Field.Item.
All props from Radix Select.Trigger, plus:
The part that reflects the selected value. By default the selected item's text will be rendered. If you require more control, you can instead control the Select and pass your own children. It should not be styled to ensure correct positioning. An optional placeholder prop is also available for when the Select has no value.
Radix Select.Value props.
The component that pops out when the Select is open as a portal adjacent to the Select.Trigger button. It contains a scrolling viewport of the select items.
All props from Radix Select.Content, plus:
A group of related options within a select menu. Similar to an html optgroup element. Use in conjunction with Select.Label to ensure good accessibility via automatic labelling.
Radix Select.Group props.
Used to visually separate items in the select. Composed from Mantle Separator.
An option within a select menu. Similar to an html option element. Has a required value prop that will be passed to the onValueChange handler of the Select component when this item is selected. Displays the children as the option's text.
Radix Select.Item props.
Used to render the label of a group. It won't be focusable using arrow keys. Use in conjunction with Select.Group to ensure good accessibility via automatic labelling of a group.
Radix Select.Label props.