Select
Pick one value from a list without search — with groups, icons and form support.
Buyurtma shu viloyatga yetkaziladi
Qiymat: —
'use client';
import { Field, Select } from '@akhatmirzo/ui';
import { useState } from 'react';
export default function SelectBasic() {
const [region, setRegion] = useState('');
return (
<div className="flex w-full max-w-xs flex-col gap-3">
<Field label="Viloyat" description="Buyurtma shu viloyatga yetkaziladi">
<Select value={region} onValueChange={setRegion}>
<Select.Item value="tsh">Toshkent shahri</Select.Item>
<Select.Item value="sam">Samarqand</Select.Item>
<Select.Item value="bux">Buxoro</Select.Item>
<Select.Item value="and">Andijon</Select.Item>
<Select.Item value="fer">Farg'ona</Select.Item>
<Select.Item value="nam">Namangan</Select.Item>
<Select.Item value="qash">Qashqadaryo</Select.Item>
<Select.Item value="xor">Xorazm</Select.Item>
<Select.Item value="qqr">Qoraqalpog'iston</Select.Item>
</Select>
</Field>
<p className="text-sm text-text-muted">
Qiymat: <span className="font-medium text-text">{region || '—'}</span>
</p>
</div>
);
}
Import
import { Select } from '@akhatmirzo/ui';When to use
- One choice out of roughly 5–15 options — a region, a payment method, a status. With three
options or fewer, a
RadioGroupworks better: everything is visible at once. - Need search? Use
Combobox.Selecthas no search on purpose; long lists like cities or products belong in a Combobox. - Several values also call for
Comboboxwithmultiple—Selectreturns a single value. - Always give it a name: put it inside a
Fieldor passaria-label. - Not for commands. Actions like "Edit" or "Delete" go in a
DropdownMenu.
Groups and separators
Select.Group puts options under a heading, and Select.Separator draws a line between groups.
The selected option gets a ✓ mark.
'use client';
import { Field, Select } from '@akhatmirzo/ui';
export default function SelectGroups() {
return (
<Field label="Filial" className="w-full max-w-xs">
<Select defaultValue="fer">
<Select.Item value="tsh">Toshkent</Select.Item>
<Select.Item value="sam">Samarqand</Select.Item>
<Select.Group label="Vodiy">
<Select.Item value="and">Andijon</Select.Item>
<Select.Item value="fer">Farg'ona</Select.Item>
<Select.Item value="nam">Namangan</Select.Item>
</Select.Group>
<Select.Separator />
<Select.Group label="Janub">
<Select.Item value="qash">Qashqadaryo</Select.Item>
<Select.Item value="sur">Surxondaryo</Select.Item>
</Select.Group>
</Select>
</Field>
);
}
<Select defaultValue="fer">
<Select.Item value="tsh">Toshkent</Select.Item>
<Select.Group label="Vodiy">
<Select.Item value="and">Andijon</Select.Item>
<Select.Item value="fer">Farg'ona</Select.Item>
</Select.Group>
<Select.Separator />
<Select.Group label="Janub">
<Select.Item value="qash">Qashqadaryo</Select.Item>
</Select.Group>
</Select>Sizes
Three sizes — 32, 40 and 48px, the same heights as Input and Button, so they line up in a
row. The panel is as wide as the trigger; long lists scroll after 320px.
'use client';
import { Button, Select } from '@akhatmirzo/ui';
export default function SelectSizes() {
return (
<div className="flex w-full max-w-md flex-col gap-4">
{(['sm', 'md', 'lg'] as const).map((size) => (
<Select
key={size}
size={size}
aria-label={`O'lcham ${size}`}
placeholder={`size="${size}"`}
>
<Select.Item value="a">Birinchi</Select.Item>
<Select.Item value="b">Ikkinchi</Select.Item>
</Select>
))}
<div className="flex items-center gap-3">
<Select aria-label="Valyuta" defaultValue="uzs" className="w-28">
<Select.Item value="uzs">UZS</Select.Item>
<Select.Item value="usd">USD</Select.Item>
<Select.Item value="eur">EUR</Select.Item>
</Select>
<Button variant="primary">To'lash</Button>
</div>
</div>
);
}
States
Inside a Field, disabled, error and required are wired up for you — no need to repeat
them on Select. Without a Field, pass invalid and disabled directly. To disable a single
option, set disabled on Select.Item; keyboard navigation skips it.
To'lov usulini tanlang
'use client';
import { Field, Select } from '@akhatmirzo/ui';
export default function SelectStates() {
return (
<div className="grid w-full gap-4 sm:grid-cols-3">
<Field label="O'chirilgan" disabled>
<Select defaultValue="click">
<Select.Item value="click">Click</Select.Item>
</Select>
</Field>
<Field label="To'lov usuli" error="To'lov usulini tanlang" required>
<Select>
<Select.Item value="click">Click</Select.Item>
<Select.Item value="payme">Payme</Select.Item>
</Select>
</Field>
<Field label="O'chirilgan element">
<Select placeholder="Usulni tanlang">
<Select.Item value="click">Click</Select.Item>
<Select.Item value="payme">Payme</Select.Item>
<Select.Item value="uzum" disabled>
Uzum (vaqtincha ishlamaydi)
</Select.Item>
<Select.Item value="cash">Naqd pul</Select.Item>
</Select>
</Field>
</div>
);
}
Options with icons
The icon prop on Select.Item reserves a 16px slot before the label. Handy for short markers
such as status colors.
'use client';
import { Field, Select } from '@akhatmirzo/ui';
function Dot({ className }: { className: string }) {
return <span className={`size-2 rounded-full ${className}`} />;
}
export default function SelectIcons() {
return (
<Field label="Buyurtma holati" className="w-full max-w-xs">
<Select defaultValue="progress">
<Select.Item value="new" icon={<Dot className="bg-info" />}>
Yangi
</Select.Item>
<Select.Item value="progress" icon={<Dot className="bg-warning" />}>
Jarayonda
</Select.Item>
<Select.Item value="done" icon={<Dot className="bg-success" />}>
Yetkazildi
</Select.Item>
<Select.Item value="canceled" icon={<Dot className="bg-danger" />}>
Bekor qilindi
</Select.Item>
</Select>
</Field>
);
}
In a form
When you pass name, Select renders a hidden native <select>, so the value is submitted with
FormData. Validate the empty case yourself and show the message through the error prop of
Field.
'use client';
import { Button, Field, Select } from '@akhatmirzo/ui';
import { useState, type FormEvent } from 'react';
export default function SelectForm() {
const [error, setError] = useState<string>();
const [sent, setSent] = useState('—');
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const delivery = String(new FormData(e.currentTarget).get('delivery') ?? '');
if (!delivery) return setError('Yetkazib berish usulini tanlang');
setError(undefined);
setSent(delivery);
};
return (
<form onSubmit={handleSubmit} className="flex w-full max-w-xs flex-col gap-4">
<Field label="Yetkazib berish" error={error} required>
<Select name="delivery" onValueChange={() => setError(undefined)}>
<Select.Item value="courier">Kuryer (1 kun)</Select.Item>
<Select.Item value="pickup">Olib ketish</Select.Item>
<Select.Item value="post">Pochta (2–3 kun)</Select.Item>
</Select>
</Field>
<div className="flex items-center gap-3">
<Button type="submit" variant="primary">
Yuborish
</Button>
<span className="text-sm text-text-muted">FormData: {sent}</span>
</div>
</form>
);
}
Inside a dialog
Select works inside a Dialog or a Drawer out of the box:
the panel detects the modal layer and raises its z-index, so the list opens above the dialog.
There is nothing to configure — see the "Select and Combobox inside a dialog" section on the
Dialog page for a working example.
Props
Select
className goes to the trigger and contentClassName to the dropdown panel. Any other
<button> attributes (id, aria-*, data-*, onBlur ...) and ref are passed to the
trigger as well.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | Selected value (controlled) |
defaultValue | string | — | Initial value (uncontrolled) |
onValueChange | (value: string) => void | — | Called when the user picks an option |
open | boolean | — | Whether the panel is open (controlled) |
defaultOpen | boolean | — | Open on first render |
onOpenChange | (open: boolean) => void | — | Called when the panel opens or closes |
placeholder | ReactNode | 'Tanlang' | Shown while nothing is selected |
size | 'sm' | 'md' | 'lg' | 'md' | Height: 32 / 40 / 48px |
invalid | boolean | — | Error state. Set automatically when Field has an error |
disabled | boolean | — | Disables the whole Select |
required | boolean | — | Required field (aria-required) |
name | string | — | Name of the hidden native select for forms |
autoComplete | string | — | Passed to the hidden native select |
dir | 'ltr' | 'rtl' | — | Text direction |
contentClassName | string | — | Class for the dropdown panel |
container | HTMLElement | null | — | Portal container (defaults to document.body) |
childrenrequired | ReactNode | — | Select.Item, Select.Group and Select.Separator |
Select.Item
| Prop | Type | Default | Description |
|---|---|---|---|
valuerequired | string | — | Option value |
icon | ReactNode | — | Leading icon (16px) |
disabled | boolean | — | Disables the option |
textValue | string | — | Text for typeahead when children aren't plain text |
Select.Group
| Prop | Type | Default | Description |
|---|---|---|---|
label | ReactNode | — | Group heading |
children | ReactNode | — | The Select.Item elements in the group |
Select.Separator is a divider line between groups; it only takes className.
Data attributes
| Attribute | On | Value |
|---|---|---|
data-size | trigger | sm · md · lg |
data-state | trigger | open · closed |
data-placeholder | trigger | present while nothing is selected |
data-invalid | trigger | present in the error state |
data-disabled | trigger | present when disabled |
data-state | option | checked · unchecked |
data-highlighted | option | present under the keyboard or the mouse |
Keyboard
| Key | Action |
|---|---|
Enter / Space / ↓ / ↑ | Opens the panel when the trigger is focused |
↓ / ↑ | Moves between options (disabled ones are skipped) |
Home / End | First / last option |
| A letter | Jumps to the next option starting with that letter |
Enter / Space | Selects and closes |
Esc | Closes without changing the value, focus returns |
Accessibility
- Roles. The trigger is
role="combobox"witharia-expanded, the panel is alistbox, and each option is anoptionwitharia-selected. It is built on Radix Select. - Field wiring. Inside a
Field, the label,descriptionanderrorare linked to the trigger througharia-labelledby/aria-describedby. Errors setaria-invalid="true";requiredsetsaria-required="true". - Focus. When the panel opens, focus moves to the selected option; on close it returns to the trigger.
- Never leave it unnamed. Without a
Field, add anaria-label(for examplearia-label="Currency"). - Decorations. The chevron, the ✓ mark and
iconarearia-hidden, so screen readers only read the option text.