Select
Select provides users with a floating element containing a list of options to choose from.
Show code 'use client' ;
import * as React from 'react' ;
import { Select } from '@base-ui-components/react/Select' ;
import { css , styled } from '@mui/system' ;
export default function SelectIntroduction () {
return (
< Select.Root defaultValue = "system" >
< SelectTrigger aria-label = "Select font" >
< Select.Value placeholder = "System font" />
< SelectDropdownArrow />
</ SelectTrigger >
< SelectPositioner sideOffset = { 5 }>
< SelectScrollUpArrow />
< SelectPopup >
< SelectOption value = "system" >
< SelectOptionIndicator render = {< CheckIcon />} />
< Select.OptionText >System font</ Select.OptionText >
</ SelectOption >
< SelectOption value = "arial" >
< SelectOptionIndicator render = {< CheckIcon />} />
< Select.OptionText >Arial</ Select.OptionText >
</ SelectOption >
< SelectOption value = "roboto" >
< SelectOptionIndicator render = {< CheckIcon />} />
< Select.OptionText >Roboto</ Select.OptionText >
</ SelectOption >
</ SelectPopup >
< SelectScrollDownArrow />
</ SelectPositioner >
</ Select.Root >
);
}
const CheckIcon = styled ( function CheckIcon ( props : React . SVGProps < SVGSVGElement >) {
return (
< svg
xmlns = "http://www.w3.org/2000/svg"
{ ... props }
width = "24"
height = "24"
viewBox = "0 0 24 24"
fill = "none"
>
< path
d = "M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"
fill = "currentColor"
/>
</ svg >
);
}) `
width: 100%;
height: 100%;
` ;
const triggerPaddingX = 6 ;
const popupPadding = 4 ;
const SelectTrigger = styled ( Select . Trigger ) `
font-family: 'IBM Plex Sans', sans-serif;
display: flex;
align-items: center;
justify-content: space-between;
padding: ${ triggerPaddingX }px 12px;
border-radius: 5px;
background-color: black;
color: white;
border: none;
font-size: 100%;
line-height: 1.5;
user-select: none;
cursor: default;
&:focus-visible {
outline: 2px solid black;
outline-offset: 2px;
}
` ;
const SelectDropdownArrow = styled ( Select . Icon ) `
margin-left: 6px;
font-size: 10px;
line-height: 1;
height: 6px;
` ;
const SelectPositioner = styled ( Select . Positioner ) `
&[data-side='none'] {
z-index: 1;
}
` ;
const SelectPopup = styled ( Select . Popup ) `
overflow-y: auto;
background-color: white;
padding: ${ popupPadding }px;
border-radius: 5px;
box-shadow:
0 2px 4px rgb(0 0 0 / 0.1),
0 0 0 1px rgb(0 0 0 / 0.1);
max-height: var(--available-height);
min-width: min(
calc(var(--available-width) - ${ popupPadding * 2 }px),
calc(var(--anchor-width) + ${ triggerPaddingX * 2 + popupPadding * 2 }px)
);
scroll-padding: ${ popupPadding }px;
&[data-side='none'] {
scroll-padding: 15px;
}
--padding: 6px;
--icon-size: 16px;
--icon-margin: 4px;
` ;
const SelectOption = styled ( Select . Option ) `
outline: 0;
cursor: default;
border-radius: 4px;
user-select: none;
display: flex;
align-items: center;
line-height: 1.5;
padding-block: var(--padding);
padding-inline: calc(var(--padding) + var(--icon-margin) + var(--icon-size));
&[data-selected] {
padding-left: var(--padding);
}
&[data-disabled] {
opacity: 0.5;
}
&[data-highlighted] {
background-color: black;
color: white;
}
` ;
const SelectOptionIndicator = styled ( Select . OptionIndicator ) `
margin-right: var(--icon-margin);
visibility: hidden;
width: var(--icon-size);
height: var(--icon-size);
&[data-selected] {
visibility: visible;
}
` ;
const scrollArrowStyles = css `
position: relative;
width: 100%;
height: 15px;
font-size: 10px;
cursor: default;
background: white;
display: flex;
align-items: center;
justify-content: center;
border-radius: 5px;
z-index: 1;
&[data-side='none'] {
&::before {
content: '';
display: block;
position: absolute;
width: 100%;
height: calc(100% + 10px);
}
}
` ;
const SelectScrollUpArrow = styled ( Select . ScrollUpArrow ) `
${ scrollArrowStyles }
&::before {
top: -10px;
}
` ;
const SelectScrollDownArrow = styled ( Select . ScrollDownArrow ) `
${ scrollArrowStyles }
bottom: 0;
&::before {
top: 0;
}
` ;
Base UI components are all available as a single package.
npm pnpm Yarn
npm install @base-ui-components/react
Once you have the package installed, import the component.
import { Select } from '@base-ui-components/react/Select';
Selects are implemented using a collection of related components:
<Select.Root />
is a top-level component that wraps the other components.
<Select.Trigger />
renders the trigger element that opens the select popup on click.
<Select.Value />
renders the value of the select.
<Select.Icon />
renders a caret icon.
<Select.Backdrop />
renders a backdrop element behind the popup.
<Select.Positioner />
renders the select popup's positioning element.
<Select.Popup />
renders the select popup itself.
<Select.Option />
renders an option, placed inside the popup.
<Select.OptionText />
renders the text of an option.
<Select.OptionIndicator />
renders an option indicator inside an option to indicate it's selected (e.g. a check icon).
<Select.Group />
renders a group for a set of options, wrapping <Select.Option>
components.
<Select.GroupLabel />
renders a label for a group of options.
<Select.ScrollUpArrow />
renders a scrolling arrow for the alignOptionToTrigger
anchoring mode.
<Select.ScrollDownArrow />
renders a scrolling arrow for the alignOptionToTrigger
anchoring mode.
<Select.Separator />
renders a separator between option groups.
<Select.Arrow />
renders the select popup's arrow when disabling alignOptionToTrigger
.
< Select.Root >
< Select.Trigger >
< Select.Value />
< Select.Icon />
</ Select.Trigger >
< Select.Backdrop />
< Select.Positioner >
< Select.ScrollUpArrow />
< Select.Popup >
< Select.Group >
< Select.GroupLabel />
< Select.Option >
< Select.OptionText />
< Select.OptionIndicator />
</ Select.Option >
</ Select.Group >
< Select.Separator />
</ Select.Popup >
< Select.ScrollDownArrow />
< Select.Arrow />
</ Select.Positioner >
</ Select.Root >
To set an initial value when uncontrolled, use defaultValue
:
< Select.Root defaultValue = "system" >
The select's value is empty (null
) by default, which enables an empty Option
to be initially selected when it has no value
prop:
Show code 'use client' ;
import * as React from 'react' ;
import { Select } from '@base-ui-components/react/Select' ;
import { css , styled } from '@mui/system' ;
export default function SelectEmpty () {
return (
< Select.Root >
< SelectTrigger aria-label = "Select font" >
< Select.Value placeholder = "Select font..." />
< SelectDropdownArrow />
</ SelectTrigger >
< SelectPositioner sideOffset = { 5 }>
< SelectScrollUpArrow />
< SelectPopup >
< SelectOption >
< SelectOptionIndicator render = {< CheckIcon />} />
< Select.OptionText >Select font...</ Select.OptionText >
</ SelectOption >
< SelectOption value = "system" >
< SelectOptionIndicator render = {< CheckIcon />} />
< Select.OptionText >System font</ Select.OptionText >
</ SelectOption >
< SelectOption value = "arial" >
< SelectOptionIndicator render = {< CheckIcon />} />
< Select.OptionText >Arial</ Select.OptionText >
</ SelectOption >
< SelectOption value = "roboto" >
< SelectOptionIndicator render = {< CheckIcon />} />
< Select.OptionText >Roboto</ Select.OptionText >
</ SelectOption >
</ SelectPopup >
< SelectScrollDownArrow />
</ SelectPositioner >
</ Select.Root >
);
}
const CheckIcon = styled ( function CheckIcon ( props : React . SVGProps < SVGSVGElement >) {
return (
< svg
xmlns = "http://www.w3.org/2000/svg"
{ ... props }
width = "24"
height = "24"
viewBox = "0 0 24 24"
fill = "none"
>
< path
d = "M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"
fill = "currentColor"
/>
</ svg >
);
}) `
width: 100%;
height: 100%;
` ;
const triggerPaddingX = 6 ;
const popupPadding = 4 ;
const SelectTrigger = styled ( Select . Trigger ) `
font-family: 'IBM Plex Sans', sans-serif;
display: flex;
align-items: center;
justify-content: space-between;
padding: ${ triggerPaddingX }px 12px;
border-radius: 5px;
background-color: black;
color: white;
border: none;
font-size: 100%;
line-height: 1.5;
user-select: none;
cursor: default;
&:focus-visible {
outline: 2px solid black;
outline-offset: 2px;
}
` ;
const SelectDropdownArrow = styled ( Select . Icon ) `
margin-left: 6px;
font-size: 10px;
line-height: 1;
height: 6px;
` ;
const SelectPositioner = styled ( Select . Positioner ) `
&[data-side='none'] {
z-index: 1;
}
` ;
const SelectPopup = styled ( Select . Popup ) `
overflow-y: auto;
background-color: white;
padding: ${ popupPadding }px;
border-radius: 5px;
box-shadow:
0 2px 4px rgb(0 0 0 / 0.1),
0 0 0 1px rgb(0 0 0 / 0.1);
max-height: var(--available-height);
min-width: min(
calc(var(--available-width) - ${ popupPadding * 2 }px),
calc(var(--anchor-width) + ${ triggerPaddingX * 2 + popupPadding * 2 }px)
);
scroll-padding: ${ popupPadding }px;
&[data-side='none'] {
scroll-padding: 15px;
}
--padding: 6px;
--icon-size: 16px;
--icon-margin: 4px;
` ;
const SelectOption = styled ( Select . Option ) `
outline: 0;
cursor: default;
border-radius: 4px;
user-select: none;
display: flex;
align-items: center;
line-height: 1.5;
padding-block: var(--padding);
padding-inline: calc(var(--padding) + var(--icon-margin) + var(--icon-size));
&[data-selected] {
padding-left: var(--padding);
}
&[data-disabled] {
opacity: 0.5;
}
&[data-highlighted] {
background-color: black;
color: white;
}
` ;
const SelectOptionIndicator = styled ( Select . OptionIndicator ) `
margin-right: var(--icon-margin);
visibility: hidden;
width: var(--icon-size);
height: var(--icon-size);
&[data-selected] {
visibility: visible;
}
` ;
const scrollArrowStyles = css `
position: relative;
width: 100%;
height: 15px;
font-size: 10px;
cursor: default;
background: white;
display: flex;
align-items: center;
justify-content: center;
border-radius: 5px;
z-index: 1;
&[data-side='none'] {
&::before {
content: '';
display: block;
position: absolute;
width: 100%;
height: calc(100% + 10px);
}
}
` ;
const SelectScrollUpArrow = styled ( Select . ScrollUpArrow ) `
${ scrollArrowStyles }
&::before {
top: -10px;
}
` ;
const SelectScrollDownArrow = styled ( Select . ScrollDownArrow ) `
${ scrollArrowStyles }
bottom: 0;
&::before {
top: 0;
}
` ;
To control the value with external state, specify the value
and onValueChange
props:
const [ value , setValue ] = React . useState ( 'system' );
return (
< Select.Root value = { value } onValueChange = { setValue }>
{ /* subcomponents */ }
</ Select.Root >
);
The Select.OptionIndicator
subcomponent renders an indicator inside an option to indicate it's selected. By default, it renders a check icon, but this can be customized:
< Select.OptionIndicator >
< MyCheckIcon />
</ Select.OptionIndicator >
Select.Group
can be used to group options together with a label. The Select.GroupLabel
subcomponent renders the label:
< Select.Group >
< Select.GroupLabel >Label</ Select.GroupLabel >
< Select.Option value = "option1" >Option 1</ Select.Option >
< Select.Option value = "option2" >Option 2</ Select.Option >
</ Select.Group >
Show code 'use client' ;
import * as React from 'react' ;
import { Select } from '@base-ui-components/react/Select' ;
import { css , styled } from '@mui/system' ;
function createOptions ( items : string []) {
return items . map (( item ) => ({
value: item ,
label: item [ 0 ]. toUpperCase () + item . slice ( 1 ),
}));
}
const data = {
Fruits: createOptions ([ 'apple' , 'banana' , 'orange' , 'pear' , 'grape' , 'pineapple' ]),
Vegetables: createOptions ([
'carrot' ,
'lettuce' ,
'broccoli' ,
'cauliflower' ,
'asparagus' ,
'zucchini' ,
]),
};
const entries = Object . entries ( data );
export default function SelectGroup () {
return (
< Select.Root >
< SelectTrigger aria-label = "Select food" >
< Select.Value placeholder = "Select food..." />
< SelectDropdownArrow />
</ SelectTrigger >
< SelectPositioner sideOffset = { 5 }>
< SelectScrollUpArrow />
< SelectPopup >
< SelectOption >
< SelectOptionIndicator render = {< CheckIcon />} />
< Select.OptionText >Select food...</ Select.OptionText >
</ SelectOption >
{ entries . map (([ group , items ]) => (
< React.Fragment key = { group }>
< SelectSeparator />
< Select.Group key = { group }>
< SelectGroupLabel >{ group }</ SelectGroupLabel >
{ items . map (( item ) => (
< SelectOption
key = { item . value }
value = { item . value }
disabled = { item . value === 'banana' }
>
< SelectOptionIndicator render = {< CheckIcon />} />
< Select.OptionText >{ item . label }</ Select.OptionText >
</ SelectOption >
))}
</ Select.Group >
</ React.Fragment >
))}
</ SelectPopup >
< SelectScrollDownArrow />
</ SelectPositioner >
</ Select.Root >
);
}
const CheckIcon = styled ( function CheckIcon ( props : React . SVGProps < SVGSVGElement >) {
return (
< svg
xmlns = "http://www.w3.org/2000/svg"
{ ... props }
width = "24"
height = "24"
viewBox = "0 0 24 24"
fill = "none"
>
< path
d = "M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"
fill = "currentColor"
/>
</ svg >
);
}) `
width: 100%;
height: 100%;
` ;
const gray = {
300 : '#e5e7eb' ,
};
const triggerPaddingX = 6 ;
const popupPadding = 4 ;
const SelectTrigger = styled ( Select . Trigger ) `
font-family: 'IBM Plex Sans', sans-serif;
display: flex;
align-items: center;
justify-content: space-between;
padding: ${ triggerPaddingX }px 12px;
border-radius: 5px;
background-color: black;
color: white;
border: none;
font-size: 100%;
line-height: 1.5;
user-select: none;
cursor: default;
&:focus-visible {
outline: 2px solid black;
outline-offset: 2px;
}
` ;
const SelectDropdownArrow = styled ( Select . Icon ) `
margin-left: 6px;
font-size: 10px;
line-height: 1;
height: 6px;
` ;
const SelectPositioner = styled ( Select . Positioner ) `
&[data-side='none'] {
z-index: 1;
}
` ;
const SelectPopup = styled ( Select . Popup ) `
overflow-y: auto;
background-color: white;
padding: ${ popupPadding }px;
border-radius: 5px;
box-shadow:
0 2px 4px rgb(0 0 0 / 0.1),
0 0 0 1px rgb(0 0 0 / 0.1);
max-height: var(--available-height);
min-width: min(
calc(var(--available-width) - ${ popupPadding * 2 }px),
calc(var(--anchor-width) + ${ triggerPaddingX * 2 + popupPadding * 2 }px)
);
scroll-padding: ${ popupPadding }px;
&[data-side='none'] {
scroll-padding: 15px;
}
--padding: 6px;
--icon-size: 16px;
--icon-margin: 4px;
` ;
const SelectOption = styled ( Select . Option ) `
outline: 0;
cursor: default;
border-radius: 4px;
user-select: none;
display: flex;
align-items: center;
line-height: 1.5;
padding-block: var(--padding);
padding-inline: calc(var(--padding) + var(--icon-margin) + var(--icon-size));
&[data-selected] {
padding-left: var(--padding);
}
&[data-disabled] {
opacity: 0.5;
}
&[data-highlighted] {
background-color: black;
color: white;
}
` ;
const SelectOptionIndicator = styled ( Select . OptionIndicator ) `
margin-right: var(--icon-margin);
visibility: hidden;
width: var(--icon-size);
height: var(--icon-size);
&[data-selected] {
visibility: visible;
}
` ;
const scrollArrowStyles = css `
position: relative;
width: 100%;
height: 15px;
font-size: 10px;
cursor: default;
background: white;
display: flex;
align-items: center;
justify-content: center;
border-radius: 5px;
z-index: 1;
&[data-side='none'] {
&::before {
content: '';
display: block;
position: absolute;
width: 100%;
height: calc(100% + 10px);
}
}
` ;
const SelectScrollUpArrow = styled ( Select . ScrollUpArrow ) `
${ scrollArrowStyles }
&::before {
top: -10px;
}
` ;
const SelectScrollDownArrow = styled ( Select . ScrollDownArrow ) `
${ scrollArrowStyles }
bottom: 0;
&::before {
top: 0;
}
` ;
const SelectGroupLabel = styled ( Select . GroupLabel ) `
font-weight: bold;
padding: var(--padding)
calc(var(--padding) + var(--icon-margin) + var(--icon-size));
cursor: default;
user-select: none;
` ;
const SelectSeparator = styled ( Select . Separator ) `
height: 1px;
background-color: ${ gray [ 300 ] };
margin: 5px 0;
` ;
By default, the selected option inside the popup is aligned to the trigger element. This can be disabled with the alignOptionToTrigger
prop:
< Select.Root alignOptionToTrigger = { false }>
alignOptionToTrigger={true}
: aligns the popup such that the selected option inside of it appears centered over the trigger. If there's not enough space, it falls back to standard anchoring. This method is useful as it allows the user to select an option in a single click or "pointer cycle" (pointer down, pointer move, pointer up). This is the native behavior on macOS; the scroll arrow components must be used to ensure a single pointer cycle can be used. The [data-side]
attribute value is none
on both Select.Positioner
and Select.Popup
when in this mode, allowing it to be styled differently.
alignOptionToTrigger={false}
: aligns the popup to the trigger itself on its top or bottom side, which is the standard form of anchor positioning used in Tooltip, Popover, Menu, etc.
alignOptionToTrigger
is always false
on touch devices or touch input.
Scrolling is locked when alignOptionToTrigger
is true
to prevent unwanted scrolling of the
background when expanding the popup, ensuring positioning remains correct.
Show code 'use client' ;
import * as React from 'react' ;
import { Select } from '@base-ui-components/react/Select' ;
import { css , styled } from '@mui/system' ;
function AlignOptionToTriggerTrue () {
return (
< Select.Root >
< SelectTrigger aria-label = "Select font" >
< Select.Value placeholder = "Align option to trigger" />
< SelectDropdownArrow />
</ SelectTrigger >
< SelectPositioner sideOffset = { 5 }>
< SelectScrollUpArrow />
< SelectPopup >
< SelectOption >
< SelectOptionIndicator render = {< CheckIcon />} />
< Select.OptionText >Align option to trigger</ Select.OptionText >
</ SelectOption >
< SelectOption value = "system" >
< SelectOptionIndicator render = {< CheckIcon />} />
< Select.OptionText >System font</ Select.OptionText >
</ SelectOption >
< SelectOption value = "arial" >
< SelectOptionIndicator render = {< CheckIcon />} />
< Select.OptionText >Arial</ Select.OptionText >
</ SelectOption >
< SelectOption value = "roboto" >
< SelectOptionIndicator render = {< CheckIcon />} />
< Select.OptionText >Roboto</ Select.OptionText >
</ SelectOption >
</ SelectPopup >
< SelectScrollDownArrow />
</ SelectPositioner >
</ Select.Root >
);
}
function AlignOptionToTriggerFalse () {
return (
< Select.Root alignOptionToTrigger = { false }>
< SelectTrigger aria-label = "Select font" >
< Select.Value placeholder = "Align popup to trigger" />
< SelectDropdownArrow />
</ SelectTrigger >
< SelectPositioner sideOffset = { 5 }>
< SelectScrollUpArrow />
< SelectPopup >
< SelectOption >
< SelectOptionIndicator render = {< CheckIcon />} />
< Select.OptionText >Align popup to trigger</ Select.OptionText >
</ SelectOption >
< SelectOption value = "system" >
< SelectOptionIndicator render = {< CheckIcon />} />
< Select.OptionText >System font</ Select.OptionText >
</ SelectOption >
< SelectOption value = "arial" >
< SelectOptionIndicator render = {< CheckIcon />} />
< Select.OptionText >Arial</ Select.OptionText >
</ SelectOption >
< SelectOption value = "roboto" >
< SelectOptionIndicator render = {< CheckIcon />} />
< Select.OptionText >Roboto</ Select.OptionText >
</ SelectOption >
</ SelectPopup >
< SelectScrollDownArrow />
</ SelectPositioner >
</ Select.Root >
);
}
export default function SelectAlign () {
return (
< div
style = {{
display: 'flex' ,
justifyContent: 'center' ,
alignItems: 'center' ,
gap: 10 ,
}}
>
< AlignOptionToTriggerTrue />
< AlignOptionToTriggerFalse />
</ div >
);
}
const CheckIcon = styled ( function CheckIcon ( props : React . SVGProps < SVGSVGElement >) {
return (
< svg
xmlns = "http://www.w3.org/2000/svg"
{ ... props }
width = "24"
height = "24"
viewBox = "0 0 24 24"
fill = "none"
>
< path
d = "M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"
fill = "currentColor"
/>
</ svg >
);
}) `
width: 100%;
height: 100%;
` ;
const triggerPaddingX = 6 ;
const popupPadding = 4 ;
const SelectTrigger = styled ( Select . Trigger ) `
font-family: 'IBM Plex Sans', sans-serif;
display: flex;
align-items: center;
justify-content: space-between;
padding: ${ triggerPaddingX }px 12px;
border-radius: 5px;
background-color: black;
color: white;
border: none;
font-size: 100%;
line-height: 1.5;
user-select: none;
cursor: default;
&:focus-visible {
outline: 2px solid black;
outline-offset: 2px;
}
` ;
const SelectDropdownArrow = styled ( Select . Icon ) `
margin-left: 6px;
font-size: 10px;
line-height: 1;
height: 6px;
` ;
const SelectPositioner = styled ( Select . Positioner ) `
&[data-side='none'] {
z-index: 1;
}
` ;
const SelectPopup = styled ( Select . Popup ) `
overflow-y: auto;
background-color: white;
padding: ${ popupPadding }px;
border-radius: 5px;
box-shadow:
0 2px 4px rgb(0 0 0 / 0.1),
0 0 0 1px rgb(0 0 0 / 0.1);
max-height: var(--available-height);
min-width: min(
calc(var(--available-width) - ${ popupPadding * 2 }px),
calc(var(--anchor-width) + ${ triggerPaddingX * 2 + popupPadding * 2 }px)
);
scroll-padding: ${ popupPadding }px;
&[data-side='none'] {
scroll-padding: 15px;
}
--padding: 6px;
--icon-size: 16px;
--icon-margin: 4px;
` ;
const SelectOption = styled ( Select . Option ) `
outline: 0;
cursor: default;
border-radius: 4px;
user-select: none;
display: flex;
align-items: center;
line-height: 1.5;
padding-block: var(--padding);
padding-inline: calc(var(--padding) + var(--icon-margin) + var(--icon-size));
&[data-selected] {
padding-left: var(--padding);
}
&[data-disabled] {
opacity: 0.5;
}
&[data-highlighted] {
background-color: black;
color: white;
}
` ;
const SelectOptionIndicator = styled ( Select . OptionIndicator ) `
margin-right: var(--icon-margin);
visibility: hidden;
width: var(--icon-size);
height: var(--icon-size);
&[data-selected] {
visibility: visible;
}
` ;
const scrollArrowStyles = css `
position: relative;
width: 100%;
height: 15px;
font-size: 10px;
cursor: default;
background: white;
display: flex;
align-items: center;
justify-content: center;
border-radius: 5px;
z-index: 1;
&[data-side='none'] {
&::before {
content: '';
display: block;
position: absolute;
width: 100%;
height: calc(100% + 10px);
}
}
` ;
const SelectScrollUpArrow = styled ( Select . ScrollUpArrow ) `
${ scrollArrowStyles }
&::before {
top: -10px;
}
` ;
const SelectScrollDownArrow = styled ( Select . ScrollDownArrow ) `
${ scrollArrowStyles }
bottom: 0;
&::before {
top: 0;
}
` ;
The select's height needs to be manually limited by its available space using CSS.
This can be achieved by using the --available-height
CSS variable:
< Select.Popup className = "SelectPopup" >
.SelectPopup {
max-height : var ( --available-height );
}
The Select.Value
subcomponent renders the selected value. This is the text content or label
of Select.Option
by default.
The placeholder
prop can be used when the value is empty. During SSR, if a default value is specified as the selected option, the value isn't available until hydration:
< Select.Trigger >
< Select.Value placeholder = "Select value..." />
</ Select.Trigger >
A function can be specified as a child to customize the rendering of the value:
< Select.Value >{( value ) => value . toLowerCase ()}</ Select.Value >
To add an arrow (caret or triangle) inside the select popup that points toward the center of the anchor element, use the Select.Arrow
component:
This is not supported when alignOptionToTrigger
is true
.
< Select.Positioner >
< Select.Popup />
< Select.Arrow />
</ Select.Positioner >
It automatically positions a wrapper element that can be styled or contain a custom SVG shape.
The select can animate when opening or closing with either:
CSS transitions
CSS animations
JavaScript animations
Here is an example of how to apply a symmetric scale and fade transition with the default conditionally-rendered behavior:
< Select.Popup className = "SelectPopup" >
< Select.Option >Option 1</ Select.Option >
</ Select.Popup >
.SelectPopup {
transform-origin : var ( --transform-origin );
transition-property : opacity, transform;
transition-duration : 0.2 s ;
/* Represents the final styles once exited */
opacity : 0 ;
transform : scale ( 0.9 );
}
/* Represents the final styles once entered */
.SelectPopup [ data-open ] {
opacity : 1 ;
transform : scale ( 1 );
}
/* Represents the initial styles when entering */
.SelectPopup [ data-entering ] {
opacity : 0 ;
transform : scale ( 0.9 );
}
Styles need to be applied in three states:
The exiting styles, placed on the base element class
The open styles, placed on the base element class with [data-state="open"]
The entering styles, placed on the base element class with [data-entering]
In newer browsers, there is a feature called @starting-style
which allows transitions to occur on open for conditionally-mounted components:
/* Base UI API - Polyfill */
.SelectPopup [ data-entering ] {
opacity : 0 ;
transform : scale ( 0.9 );
}
/* Official Browser API - no Firefox support as of May 2024 */
@starting-style {
.SelectPopup [ data-open ] {
opacity : 0 ;
transform : scale ( 0.9 );
}
}
CSS animations can also be used, requiring only two separate declarations:
@keyframes scale-in {
from {
opacity : 0 ;
transform : scale ( 0.9 );
}
}
@keyframes scale-out {
to {
opacity : 0 ;
transform : scale ( 0.9 );
}
}
.SelectPopup {
animation : scale-in 0.2 s forwards ;
}
.SelectPopup [ data-exiting ] {
animation : scale-out 0.2 s forwards ;
}
The keepMounted
prop lets an external library control the mounting, for example framer-motion
's AnimatePresence
component.
function App () {
const [ open , setOpen ] = useState ( false );
return (
< Select.Root open = { open } onOpenChange = { setOpen }>
< Select.Trigger >Trigger</ Select.Trigger >
< AnimatePresence >
{ open && (
< Select.Positioner keepMounted >
< Select.Popup
render = {
< motion.div
initial = {{ opacity: 0 }}
animate = {{ opacity: 1 }}
exit = {{ opacity: 0 }}
/>
}
>
< Select.Option >Option 1</ Select.Option >
< Select.Option >Option 2</ Select.Option >
</ Select.Popup >
</ Select.Positioner >
)}
</ AnimatePresence >
</ Select.Root >
);
}
Four states are available as data attributes to animate the popup, which enables full control depending on whether the popup is being animated with CSS transitions or animations, JavaScript, or is using the keepMounted
prop.
[data-open]
- open
state is true
.
[data-entering]
- the popup was just inserted to the DOM. The attribute is removed 1 animation frame later. Enables "starting styles" upon insertion for conditional rendering.
[data-exiting]
- the popup is in the process of being removed from the DOM, but is still mounted.
Use the render
prop to override the rendered elements with your own components.
// Element shorthand
< Select.Popup render = {< MySelectPopup />} />
// Function
< Select.Popup render = {( props ) => < MySelectPopup { ... props } />} />
API Reference SelectRoot
Prop Type Default Description alignOptionToTrigger
bool
true
Determines if the selected option inside the popup should align to the trigger element. animated
bool
true
If true
, the Select supports CSS-based animations and transitions. It is kept in the DOM until the animation completes. defaultOpen
bool
false
If true
, the Select is initially open. defaultValue
any
null
The default value of the select. disabled
bool
false
If true
, the Select is disabled. name
string
The name of the Select in the owning form. onOpenChange
func
Callback fired when the component requests to be opened or closed. onValueChange
func
Callback fired when the value of the select changes. Use when controlled. open
bool
Allows to control whether the dropdown is open. This is a controlled counterpart of defaultOpen
. readOnly
bool
false
If true
, the Select is read-only. required
bool
false
If true
, the Select is required. value
any
The value of the select.
SelectTrigger
Prop Type Default Description className
union
Class names applied to the element or a function that returns them based on the component's state. disabled
bool
false
If true
, the component is disabled. focusableWhenDisabled
bool
false
If true
, allows a disabled button to receive focus. label
string
Label of the button render
union
A function to customize rendering of the component.
SelectValue
Prop Type Default Description className
union
Class names applied to the element or a function that returns them based on the component's state. placeholder
string
The placeholder value to display when the value is empty (such as during SSR). render
union
A function to customize rendering of the component.
SelectIcon
Prop Type Default Description className
union
Class names applied to the element or a function that returns them based on the component's state. render
union
A function to customize rendering of the component.
SelectBackdrop
Prop Type Default Description className
union
Class names applied to the element or a function that returns them based on the component's state. container
union
false
The container element to which the Backdrop is appended to. keepMounted
bool
false
If true
, the Backdrop remains mounted when the Select popup is closed. render
union
A function to customize rendering of the component.
SelectPositioner
Prop Type Default Description alignment
enum
'start'
The alignment of the Select element to the anchor element along its cross axis. alignmentOffset
number
0
The offset of the Select element along its alignment axis. anchor
union
The anchor element to which the Select popup will be placed at. arrowPadding
number
5
Determines the padding between the arrow and the Select popup's edges. Useful when the popover popup has rounded corners via border-radius
. className
union
Class names applied to the element or a function that returns them based on the component's state. collisionBoundary
union
'clipping-ancestors'
The boundary that the Select element should be constrained to. collisionPadding
union
5
The padding of the collision boundary. container
union
The container element to which the Select popup will be appended to. hideWhenDetached
bool
false
If true
, the Select will be hidden if it is detached from its anchor element due to differing clipping contexts. positionMethod
enum
'absolute'
The CSS position method for positioning the Select popup element. render
union
A function to customize rendering of the component. side
enum
'bottom'
The side of the anchor element that the Select element should align to. sideOffset
number
0
The gap between the anchor element and the Select element. sticky
bool
false
If true
, allow the Select to remain in stuck view while the anchor element is scrolled out of view. trackAnchor
bool
true
Whether the select popup continuously tracks its anchor after the initial positioning upon mount.
Prop Type Default Description union
Class names applied to the element or a function that returns them based on the component's state. string
The id of the popup element. union
A function to customize rendering of the component.
SelectOption
Prop Type Default Description disabled
bool
false
If true
, the select option will be disabled. label
string
A text representation of the select option's content. Used for keyboard text navigation matching. value
any
null
The value of the select option.
SelectOptionText
Prop Type Default Description
SelectOptionIndicator
Prop Type Default Description className
union
Class names applied to the element or a function that returns them based on the component's state. keepMounted
bool
false
If true
, the item indicator remains mounted when the item is not selected. render
union
A function to customize rendering of the component.
SelectGroup
Prop Type Default Description className
union
Class names applied to the element or a function that returns them based on the component's state. render
union
A function to customize rendering of the component.
SelectGroupLabel
Prop Type Default Description className
union
Class names applied to the element or a function that returns them based on the component's state. render
union
A function to customize rendering of the component.
Prop Type Default Description keepMounted
bool
false
Whether the component should be kept mounted when it is not rendered.
Prop Type Default Description keepMounted
bool
false
Whether the component should be kept mounted when it is not rendered.
SelectArrow
Prop Type Default Description className
union
Class names applied to the element or a function that returns them based on the component's state. hideWhenUncentered
bool
false
If true
, the arrow is hidden when it can't point to the center of the anchor element. render
union
A function to customize rendering of the component.