Overview
Listbox has no variables of its own. The list is a plain scrolling surface styled with global tokens, and its rows are Option elements styled with the --option-* variables. Height, width and border are ordinary CSS on the list.
Public CSS Variables
These variables belong to the option rows that ListboxOption renders.
| Variable | Value | Affects |
|---|---|---|
Row height, set by sizeListboxOption | No visible change in this state | |
Row radius as a share of its heightListboxOption | No visible change in this state | |
Text colorListboxOption | No visible change in this state | |
Default backgroundListboxOption | No visible change in this state | |
Background on hover and for the active optionListboxOption | No visible change in this state | |
Background while pressedListboxOption | No visible change in this state |
The list itself reads global tokens:
| Token | Used By | Purpose |
|---|---|---|
--surface-secondary | Listbox | List background |
--focus-color | Listbox | Focus ring of the list |
Part-Level Variables
Listbox
Defined in packages/ui/src/Listbox/style.module.css. The list is a flex column with padding: var(--spacing-4), gap: var(--spacing-3), border-radius: var(--spacing-8) and overflow: auto. It has no border and no height limit: it grows with its options until you set height or max-height, then scrolls.
ListboxGroup
A flex column with the same gap: var(--spacing-3) as the list, so grouped and ungrouped options keep one rhythm. It has no surface and no heading style; style your heading yourself.
ListboxOption
ListboxOption defines all --option-* variables on the row itself, so values set on Listbox do not reach it. Override them with a selector that targets the rows, or with style on each option.
size on the list sets --option-height and the type scale of every row, and size on an option overrides it:
xs:var(--spacing-16)sm:var(--spacing-18)md:var(--spacing-21), the defaultlg:var(--spacing-24)xl:var(--spacing-26)
The height is fixed, so rows with two lines of content need height: 'auto'.
State And Variant Interaction
aria-selected='true'switches the row to the primary colors.data-active='true'marks the option the list points to witharia-activedescendantand gives it the hover background, so the keyboard position stays visible while the focus is on the list.:focus-visibleon the list draws a 2px outline in--focus-color. Options never take the focus themselves.aria-disabled='true'on a row uses the disabled text color and anot-allowedcursor;disabledon the list sets it on every row.- With
multiple, adjacent selected rows join: their facing corners are squared and the gap between them is filled, so a run of choices reads as one block.
The size, selected and disabled values sit in :where(), which adds no specificity to the row class, so a selector of two classes or attributes on your side overrides them.
Examples
Selected rows in another color
A class on the list targets its rows and remaps the selected colors and the radius, leaving unselected rows as they are.
import { Listbox, ListboxOption } from '@vega-ui/react';
import './examples.css';
export default function SelectedRowsColor() {
return (
<Listbox
aria-label='Reminder before the meeting'
defaultValue='10'
className='listbox-accent'
style={{ width: '100%', maxWidth: 280, border: '1px solid var(--separator-opaque)' }}
>
<ListboxOption value='0'>At the start time</ListboxOption>.listbox-accent [role='option'][aria-selected='true'] {
--option-background-color: var(--color-green-accent-800);
--option-background-color-hover: var(--color-green-accent-900);
--option-background-color-active: var(--color-green-accent-1000);
}
.listbox-accent [role='option'] {
--option-br-ratio: 0.5;
}Compact rows in a list of fixed height
size='sm' makes every row shorter, and maxHeight turns the list into a scrolling area that keeps the active option in view.
import { Listbox, ListboxOption, Text } from '@vega-ui/react';
import { useState } from 'react';
const builds = Array.from({ length: 300 }, (_, index) => {
const number = 1480 - index;
const branch = index % 7 === 0 ? 'release/4.2' : 'main';
return { value: number, label: `Build ${number}`, branch };
});
export default function ManyOptions() {
const [build, setBuild] = useState<number | null>(1480);
Joined selected rows
With multiple, choose adjacent toppings to see the selected rows merge into one block.
import { Button, Listbox, ListboxOption, Text } from '@vega-ui/react';
import { useState } from 'react';
const toppings = [
{ value: 'cheese', label: 'Extra mozzarella', price: 1.5 },
{ value: 'mushrooms', label: 'Mushrooms', price: 1 },
{ value: 'olives', label: 'Black olives', price: 1 },
{ value: 'pepperoni', label: 'Pepperoni', price: 2 },
{ value: 'jalapenos', label: 'Jalapeños', price: 0.5 },
];
export default function SeveralChoices() {Do Not Override
- the
outlineof the list on:focus-visible: it is the only focus indicator, because options never take the focus - the background of
data-active='true'rows: it is how keyboard users see where they are - the selected colors in a way that makes selected and unselected rows look alike
overflowon the list: without it a list with a height limit stops scrolling and the active option can leave the view