Displays a list of options for the user to pick from—triggered by a button.
import React from 'react';
import { styled } from '@stitches/react';
import { violet, mauve, blackA } from '@radix-ui/colors';
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from '@radix-ui/react-icons';
import * as SelectPrimitive from '@radix-ui/react-select';
const StyledTrigger = styled(SelectPrimitive.SelectTrigger, {
all: 'unset',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 4,
padding: '0 15px',
fontSize: 13,
lineHeight: 1,
height: 35,
gap: 5,
backgroundColor: 'white',
color: violet.violet11,
boxShadow: `0 2px 10px ${blackA.blackA7}`,
'&:hover': { backgroundColor: mauve.mauve3 },
'&:focus': { boxShadow: `0 0 0 2px black` },
});
const StyledContent = styled(SelectPrimitive.Content, {
overflow: 'hidden',
backgroundColor: 'white',
borderRadius: 6,
boxShadow:
'0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2)',
});
const StyledViewport = styled(SelectPrimitive.Viewport, {
padding: 5,
});
const StyledItem = styled(SelectPrimitive.Item, {
all: 'unset',
fontSize: 13,
lineHeight: 1,
color: violet.violet11,
borderRadius: 3,
display: 'flex',
alignItems: 'center',
height: 25,
padding: '0 35px 0 25px',
position: 'relative',
userSelect: 'none',
'&[data-disabled]': {
color: mauve.mauve8,
pointerEvents: 'none',
},
'&:focus': {
backgroundColor: violet.violet9,
color: violet.violet1,
},
});
const StyledLabel = styled(SelectPrimitive.Label, {
padding: '0 25px',
fontSize: 12,
lineHeight: '25px',
color: mauve.mauve11,
});
const StyledSeparator = styled(SelectPrimitive.Separator, {
height: 1,
backgroundColor: violet.violet6,
margin: 5,
});
const StyledItemIndicator = styled(SelectPrimitive.ItemIndicator, {
position: 'absolute',
left: 0,
width: 25,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
});
const scrollButtonStyles = {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: 25,
backgroundColor: 'white',
color: violet.violet11,
cursor: 'default',
};
const StyledScrollUpButton = styled(SelectPrimitive.ScrollUpButton, scrollButtonStyles);
const StyledScrollDownButton = styled(SelectPrimitive.ScrollDownButton, scrollButtonStyles);
// Exports
export const Select = SelectPrimitive.Root;
export const SelectTrigger = StyledTrigger;
export const SelectValue = SelectPrimitive.Value;
export const SelectIcon = SelectPrimitive.Icon;
export const SelectContent = StyledContent;
export const SelectViewport = StyledViewport;
export const SelectGroup = SelectPrimitive.Group;
export const SelectItem = StyledItem;
export const SelectItemText = SelectPrimitive.ItemText;
export const SelectItemIndicator = StyledItemIndicator;
export const SelectLabel = StyledLabel;
export const SelectSeparator = StyledSeparator;
export const SelectScrollUpButton = StyledScrollUpButton;
export const SelectScrollDownButton = StyledScrollDownButton;
// Your app...
const Box = styled('div', {});
export const SelectDemo = () => (
<Box>
<Select defaultValue="blueberry">
<SelectTrigger aria-label="Food">
<SelectValue />
<SelectIcon>
<ChevronDownIcon />
</SelectIcon>
</SelectTrigger>
<SelectContent>
<SelectScrollUpButton>
<ChevronUpIcon />
</SelectScrollUpButton>
<SelectViewport>
<SelectGroup>
<SelectLabel>Fruits</SelectLabel>
<SelectItem value="apple">
<SelectItemText>Apple</SelectItemText>
<SelectItemIndicator>
<CheckIcon />
</SelectItemIndicator>
</SelectItem>
<SelectItem value="banana">
<SelectItemText>Banana</SelectItemText>
<SelectItemIndicator>
<CheckIcon />
</SelectItemIndicator>
</SelectItem>
<SelectItem value="blueberry">
<SelectItemText>Blueberry</SelectItemText>
<SelectItemIndicator>
<CheckIcon />
</SelectItemIndicator>
</SelectItem>
<SelectItem value="grapes">
<SelectItemText>Grapes</SelectItemText>
<SelectItemIndicator>
<CheckIcon />
</SelectItemIndicator>
</SelectItem>
<SelectItem value="pineapple">
<SelectItemText>Pineapple</SelectItemText>
<SelectItemIndicator>
<CheckIcon />
</SelectItemIndicator>
</SelectItem>
</SelectGroup>
<SelectSeparator />
<SelectGroup>
<SelectLabel>Vegetables</SelectLabel>
<SelectItem value="aubergine">
<SelectItemText>Aubergine</SelectItemText>
<SelectItemIndicator>
<CheckIcon />
</SelectItemIndicator>
</SelectItem>
<SelectItem value="broccoli">
<SelectItemText>Broccoli</SelectItemText>
<SelectItemIndicator>
<CheckIcon />
</SelectItemIndicator>
</SelectItem>
<SelectItem value="carrot" disabled>
<SelectItemText>Carrot</SelectItemText>
<SelectItemIndicator>
<CheckIcon />
</SelectItemIndicator>
</SelectItem>
<SelectItem value="courgette">
<SelectItemText>Courgette</SelectItemText>
<SelectItemIndicator>
<CheckIcon />
</SelectItemIndicator>
</SelectItem>
<SelectItem value="leek">
<SelectItemText>leek</SelectItemText>
<SelectItemIndicator>
<CheckIcon />
</SelectItemIndicator>
</SelectItem>
</SelectGroup>
<SelectSeparator />
<SelectGroup>
<SelectLabel>Meat</SelectLabel>
<SelectItem value="beef">
<SelectItemText>Beef</SelectItemText>
<SelectItemIndicator>
<CheckIcon />
</SelectItemIndicator>
</SelectItem>
<SelectItem value="chicken">
<SelectItemText>Chicken</SelectItemText>
<SelectItemIndicator>
<CheckIcon />
</SelectItemIndicator>
</SelectItem>
<SelectItem value="lamb">
<SelectItemText>Lamb</SelectItemText>
<SelectItemIndicator>
<CheckIcon />
</SelectItemIndicator>
</SelectItem>
<SelectItem value="pork">
<SelectItemText>Pork</SelectItemText>
<SelectItemIndicator>
<CheckIcon />
</SelectItemIndicator>
</SelectItem>
</SelectGroup>
</SelectViewport>
<SelectScrollDownButton>
<ChevronDownIcon />
</SelectScrollDownButton>
</SelectContent>
</Select>
</Box>
);
export default SelectDemo;
Install the component from your command line.
npm install @radix-ui/react-select
Import all parts and piece them together.
import * as Select from '@radix-ui/react-select';
export default () => (
<Select.Root>
<Select.Trigger>
<Select.Value />
<Select.Icon />
</Select.Trigger>
<Select.Content>
<Select.ScrollUpButton />
<Select.Viewport>
<Select.Item>
<Select.ItemText />
<Select.ItemIndicator />
</Select.Item>
<Select.Group>
<Select.Label />
<Select.Item>
<Select.ItemText />
<Select.ItemIndicator />
</Select.Item>
</Select.Group>
<Select.Separator />
</Select.Viewport>
<Select.ScrollDownButton />
</Select.Content>
</Select.Root>
);
Contains all the parts of a select.
Prop | Type | Default |
---|---|---|
defaultValue | string | |
value | string | |
onValueChange | function | |
defaultOpen | boolean | |
open | boolean | |
onOpenChange | function | |
dir | enum | "ltr" |
name | string |
The button that toggles the select. The Select.Content
will position itself by aligning over the trigger.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
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.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
A small icon often displayed next to the value as a visual affordance for the fact it can be open. By default renders ▼ but you can use your own icon via asChild
or use children
.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
The component that pops out when the select is open.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
onCloseAutoFocus | function | |
onEscapeKeyDown | function | |
onPointerDownOutside | function |
The scrolling viewport that contains all of the items.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
The component that contains the select items.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
value* | string | |
disabled | boolean | |
textValue | string |
The textual part of the item. It should only contain the text you want to see in the trigger when that item is selected. It should not be styled to ensure correct positioning.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
Renders when the item is selected. You can style this element directly, or you can use it as a wrapper to put an icon into, or both.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
An optional button used as an affordance to show the viewport overflow as well as functionaly enable scrolling upwards.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
An optional button used as an affordance to show the viewport overflow as well as functionaly enable scrolling downwards.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
Used to group multiple items. use in conjunction with Select.Label
to ensure good accessibility via automatic labelling.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
Used to render the label of a group. It won't be focusable using arrow keys.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
Used to visually separate items in the select.
Prop | Type | Default |
---|---|---|
asChild | boolean | false |
You can add special styles to disabled items via the data-disabled
attribute.
import { styled } from '@stitches/react';
import * as Select from '@radix-ui/react-select';
const StyledItem = styled(Select.Item, {
'&[data-disabled]': { color: 'gainsboro' },
});
export default () => (
<Select.Root>
<Select.Trigger>…</Select.Trigger>
<Select.Content>
<Select.Viewport>
<StyledItem disabled>…</StyledItem>
<StyledItem>…</StyledItem>
<StyledItem>…</StyledItem>
</Select.Viewport>
</Select.Content>
</Select.Root>
);
Use the Separator
part to add a separator between items.
<Select.Root>
<Select.Trigger>…</Select.Trigger>
<Select.Content>
<Select.Viewport>
<Select.Item>…</Select.Item>
<Select.Item>…</Select.Item>
<Select.Item>…</Select.Item>
<Select.Separator />
<Select.Item>…</Select.Item>
<Select.Item>…</Select.Item>
</Select.Viewport>
</Select.Content>
</Select.Root>
Use the Group
and Label
parts to group items in a section.
<Select.Root>
<Select.Trigger>…</Select.Trigger>
<Select.Content>
<Select.Viewport>
<Select.Group>
<Select.Label>Label</Select.Label>
<Select.Item>…</Select.Item>
<Select.Item>…</Select.Item>
<Select.Item>…</Select.Item>
</Select.Group>
</Select.Viewport>
</Select.Content>
</Select.Root>
You can use custom content in your items.
import { styled } from '@stitches/react';
import * as Select from '@radix-ui/react-select';
const Image = styled('img', {
width: 24,
height: 24,
});
export default () => (
<Select.Root>
<Select.Trigger>…</Select.Trigger>
<Select.Content>
<Select.Viewport>
<Select.Item>
<Select.ItemText>
<Image src="…" />
Adolfo Hess
</Select.ItemText>
<Select.ItemIndicator>…</SelectItemIndicator>
</Select.Item>
<Select.Item>…</Select.Item>
<Select.Item>…</Select.Item>
</Select.Viewport>
</Select.Content>
</Select.Root>
);
By default the trigger will automatically display the selected item ItemText
's content. You can control what appears by chosing to put things inside/outside the ItemText
part.
If you need more flexibility, you can control the component using value
/onValueChange
props and passing children
to SelectValue
. Remember to make sure what you put in there is accessible.
const countries = { france: '🇫🇷', 'united-kingdom': '🇬🇧', spain: '🇪🇸' };
export default () => {
const [value, setValue] = React.useState('france');
return (
<Select.Root value={value} onValueChange={setValue}>
<Select.Trigger>
<Select.Value aria-label={value}>
{countries[value]}
</Select.Value>
<Select.Icon />
</Select.Trigger>
<Select.Content>
<Select.Viewport>
<Select.Item value="france">
<Select.ItemText>France</Select.ItemText>
<Select.ItemIndicator>…</SelectItemIndicator>
</Select.Item>
<Select.Item value="united-kingdom">
<Select.ItemText>United Kingdom</Select.ItemText>
<Select.ItemIndicator>…</SelectItemIndicator>
</Select.Item>
<Select.Item value="spain">
<Select.ItemText>Spain</Select.ItemText>
<Select.ItemIndicator>…</SelectItemIndicator>
</Select.Item>
</Select.Viewport>
</Select.Content>
</Select.Root>
);
};
The native scrollbar is hidden by default as we recommend using the ScrollUpButton
and ScrollDownButton
parts for the best UX. If you do not want to use these parts, compose your select with our Scroll Area primitive.
import { styled } from '@stitches/react';
import * as Select from '@radix-ui/react-select';
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
const ScrollArea = styled(ScrollAreaPrimitive.Root, {
width: '100%',
height: '100%',
});
const ScrollAreaViewport = styled(ScrollAreaPrimitive.Viewport, {
width: '100%',
height: '100%',
});
const ScrollAreaScrollbar = styled(ScrollAreaPrimitive.Scrollbar, {
width: 4,
padding: '5px 2px',
});
const ScrollAreaThumb = styled(ScrollAreaPrimitive.Thumb, {
background: 'rgba(0, 0, 0, 0.3)',
borderRadius: 3,
});
export default () => (
<Select.Root>
<Select.Trigger>…</Select.Trigger>
<Select.Content>
<ScrollArea type="auto">
<Select.Viewport asChild>
<ScrollAreaViewport>
<StyledItem>…</StyledItem>
<StyledItem>…</StyledItem>
<StyledItem>…</StyledItem>
</ScrollAreaViewport>
</Select.Viewport>
<ScrollAreaScrollbar orientation="vertical">
<ScrollAreaThumb />
</ScrollAreaScrollbar>
</ScrollArea>
</Select.Content>
</Select.Root>
);
Adheres to the ListBox WAI-ARIA design pattern.
See the W3C Select-Only Combobox example for more information.
Key | Description |
---|---|
Space | When focus is on Select.Trigger , opens the select and focuses the selected item.When focus is on an item, selects the focused item. |
Enter | When focus is on Select.Trigger , opens the select and focuses the first item.When focus is on an item, selects the focused item. |
ArrowDown | When focus is on Select.Trigger , opens the select.When focus is on an item, moves focus to the next item. |
ArrowUp | When focus is on Select.Trigger , opens the select.When focus is on an item, moves focus to the previous item. |
Esc | Closes the select and moves focus to Select.Trigger . |
Use our Label component in order to offer a visual and accessible label for the select.
import * as Select from '@radix-ui/react-select';
import { Label } from '@radix-ui/react-label';
export default () => (
<>
<Label>
Country
<Select.Root>…</Select.Root>
</Label>
{/* or */}
<Label htmlFor="country">Country</Label>
<Select.Root>
<Select.Trigger id="country">…</Select.Trigger>…
</Select.Root>
</>
);
Create your own API by abstracting the primitive parts into your own component.
This example abstracts most of the parts.
import { Select, SelectItem } from './your-select';
export default () => (
<Select defaultValue="2">
<SelectItem value="1">Item 1</SelectItem>
<SelectItem value="2">Item 2</SelectItem>
<SelectItem value="3">Item 3</SelectItem>
</Select>
);
// your-select.js
import React from 'react';
import * as SelectPrimitive from '@radix-ui/react-select';
import {
CheckIcon,
ChevronDownIcon,
ChevronUpIcon,
} from '@radix-ui/react-icons';
export const Select = React.forwardRef(
({ children, ...props }, forwardedRef) => {
return (
<SelectPrimitive.Root {...props}>
<SelectPrimitive.Trigger ref={forwardedRef}>
<SelectPrimitive.Value />
<SelectPrimitive.Icon>
<ChevronDownIcon />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
<SelectPrimitive.Content>
<SelectPrimitive.ScrollUpButton>
<ChevronUpIcon />
</SelectPrimitive.ScrollUpButton>
<SelectPrimitive.Viewport>{children}</SelectPrimitive.Viewport>
<SelectPrimitive.ScrollDownButton>
<ChevronDownIcon />
</SelectPrimitive.ScrollDownButton>
</SelectPrimitive.Content>
</SelectPrimitive.Root>
);
}
);
export const SelectItem = React.forwardRef(
({ children, ...props }, forwardedRef) => {
return (
<SelectPrimitive.Item {...props} ref={forwardedRef}>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
<SelectPrimitive.ItemIndicator>
<CheckIcon />
</SelectPrimitive.ItemIndicator>
</SelectPrimitive.Item>
);
}
);