Inputs
Picker
The frame a set of wheels sits in: the selection band across the middle, the fade at the top and bottom edges, and — when you give it onconfirm — the cancel/confirm row under them.
import { Picker } from 'omaris' Examples
Basic
A wheel of dates, in the order this locale writes them. Flick it.
<script lang="ts"> import { DatePicker } from 'omaris'; let date = $state<Date | null>(new Date(2021, 8, 17));</script><div class="w-80 max-w-full"> <DatePicker surface="card" title="Date of birth" bind:value={date} monthFormat="short" /></div> Time
AM/PM on the wheels, always; the value underneath stays "14:30", so it sorts and stores without a second format.
stored as 09:30
<script lang="ts"> import { TimePicker } from 'omaris'; let opensAt = $state('09:30');</script><div class="w-64 max-w-full"> <TimePicker surface="card" title="Opens at" bind:value={opensAt} minuteStep={15} /> <p class="pt-3 text-center text-sm text-muted-foreground"> stored as <code class="font-mono">{opensAt}</code> </p></div> Columns
Picker is only the frame — the band, the fade and the actions. Put any wheels you like inside it.
<script lang="ts"> import { Picker, PickerColumn, numberOptions } from 'omaris'; let hours = $state(1); let minutes = $state(30);</script><div class="w-72 max-w-full"> <Picker surface="card" title="Duration"> <PickerColumn label="Hours" options={numberOptions(0, 12)} bind:value={hours} unit="hr" loop /> <PickerColumn label="Minutes" options={numberOptions(0, 55, { step: 5, pad: true })} bind:value={minutes} unit="min" loop /> </Picker></div> Sheet
Where a picker belongs on a phone: in a sheet, with the confirm row the onconfirm and oncancel handlers draw for you.
<script lang="ts"> import { Button, DateTimePicker, Sheet, latn } from 'omaris'; let open = $state(false); let starts = $state<Date | null>(new Date()); let draft = $state<Date | null>(new Date()); const format = new Intl.DateTimeFormat( undefined, latn({ dateStyle: 'medium', timeStyle: 'short' }) );</script><div class="flex flex-col items-center gap-3"> <Button variant="outlined" onclick={() => { draft = starts; open = true; }} > {starts ? format.format(starts) : 'Pick a time'} </Button> <Sheet bind:open side="bottom" title="Starts"> <DateTimePicker bind:value={draft} minuteStep={15} onconfirm={() => { starts = draft; open = false; }} oncancel={() => (open = false)} /> </Sheet></div> Overridden
Every part is reachable: a full-bleed band in the accent container, a larger face, seven rows deep instead of five.
<script lang="ts"> import { NumberPicker } from 'omaris'; let weight = $state(72);</script><div class="w-44 max-w-full"> <NumberPicker surface="card" title="Weight" bind:value={weight} min={40} max={140} unit="kg" visible={7} classes={{ band: 'inset-x-0 rounded-none bg-primary-container' }} columnClasses={{ face: 'text-xl font-semibold' }} /></div> When to use it
Use it for
- Touch first. A wheel covers a hundred values in one flick. Use it on a phone, a tablet or a kiosk; on a desktop a typed field or a Select is faster.
- A date far from today, like a birthday or an expiry, with
DatePicker. Three wheels beat thirteen taps through a calendar. - A time, with
TimePicker. The wheels are always 1–12 and AM/PM — omaris never shows a 24-hour clock, and there is no prop to make it. The value stays"14:30"underneath, so it sorts and stores as is. - A moment, with
DateTimePicker: one wheel of whole days ("Today", "Wed 17 Sep") beside the hour and minute. - A number out of a long run, like a weight or an age, with
NumberPicker. Anything else with your own wheels:Pickeris the frame (band, fade, confirm row) andPickerColumnis one wheel in it.
Not for
- A date near today, or one picked by looking at the week → a Date Field or Calendar. A wheel hides the shape of the month.
- A number typed more often than spun, like a quantity or a price → Number Input or Price Input.
- Fewer than about eight choices → Select, Radio or Segmented Button.
- A value that needs searching → Combobox.
- A desktop-only form where every field is typed. One wheel in a column of text fields costs more than it saves.
Do
- Put it in a Sheet on a phone and a Dialog on a desktop. Pass
onconfirmandoncancelso the picker draws its own confirm row. - Edit a copy of the value and commit it on confirm, so Cancel has something to undo.
- Give every wheel a
label. It is the accessible name. - Use
loopfor a cycle (hours, minutes, months), never for a bounded run like a year. Keep looping wheels short; the list is repeated to fake the ends. - Set
minuteStepto what the value really has: 15 for opening hours, 5 for a booking, 1 only for an alarm. UsemonthFormat="short"(or"numeric") when three wheels share a phone's width.
Don't
- Show the stored
"14:30"to a person. It is data; read it back throughIntl.DateTimeFormatwithlatn(), which pins the 12-hour clock. - Stack more than four wheels. Each one gets too narrow to read or hit.
- Use it for a value with no natural order, like a country or a category.
- Put one inside a scrolling column without room around it. A wheel takes the vertical drag that would otherwise scroll the page.
- Reimplement the momentum. The scrolling is the browser's own.
Quick reference
size Pickersmmd(default)lg
surface Pickerplain(default)cardfilled
How the frame itself is drawn.
size PickerColumnsmmd(default)lg
API
Picker
The frame a set of wheels sits in: the selection band across the middle, the fade at the top and bottom edges, and — when you give it onconfirm — the cancel/confirm row under them.
It is a surface, not an overlay. Put it in a Sheet on a phone or a Dialog on a desktop; drop it straight into a form when the value is the point of the screen.
Built for a thumb first. A wheel is the fastest way through a hundred values on a touch screen and the slowest on a desktop, where a Select or a typed field wins.
import { Picker } from 'omaris' <Picker title="Duration"> <PickerColumn label="Hours" options={hours} bind:value={h} unit="h" /> <PickerColumn label="Minutes" options={minutes} bind:value={m} unit="m" /></Picker> Parts
Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.
root- No description in the source yet.
header- The title line above the wheels.
stage- Everything the band is drawn behind: the wheels and both fades.
band- The strip marking the centre row.
wheels- The row of wheels. Masked so rows dissolve towards the edges.
actions- The cancel/confirm row. Only rendered when
onconfirmis given. action- One of those buttons.
Props
size Defaults to 'md'
PickerSize Row height and type scale.
smmdlg
surface Defaults to 'plain'
PickerSurface How the frame is drawn.
plain- No frame — the wheels sit on whatever is behind them.
card- A card: a border, the popover background and a soft shadow.
filled- A tinted well, for a picker inside a form.
visible number Rows on screen at once. Rounded up to the next odd number.
title string A title above the wheels.
disabled Defaults to false
boolean Disables every wheel inside.
haptics Defaults to true
boolean Play a tick as each row passes the centre, once haptics are on.
onconfirm () => void Renders the action row. Called with the value already committed.
oncancel () => void Renders a cancel button beside it.
confirmLabel Defaults to 'OK'
string cancelLabel Defaults to 'Cancel'
string footer Snippet Replaces the built-in action row.
children Snippet The wheels — one PickerColumn each.
class string classes PickerClasses Per-part Tailwind overrides. class still covers the root.
DatePicker
Three wheels — day, month, year — in the order this locale writes them.
The day wheel is rebuilt from the month and the year, so February never offers a 30th and a leap year does offer the 29th. min and max grey out the rows outside the range rather than removing them, so the wheel does not change length under the finger.
import { DatePicker } from 'omaris' <DatePicker bind:value={birthday} max={new Date()} monthFormat="short" /> Parts
Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.
root- No description in the source yet.
header- The title line above the wheels.
stage- Everything the band is drawn behind: the wheels and both fades.
band- The strip marking the centre row.
wheels- The row of wheels. Masked so rows dissolve towards the edges.
actions- The cancel/confirm row. Only rendered when
onconfirmis given. action- One of those buttons.
Props
value bindableDefaults to null
Date | null The date. Bindable. null until a wheel is touched.
min Date Earliest selectable date. Also the first year on the wheel.
max Date Latest selectable date. Also the last year on the wheel.
locale string BCP-47 tag for the month names and the column order.
monthFormat Defaults to 'long'
'long' | 'short' | 'numeric' How the months read. numeric for a wheel with no room for names.
weekday Defaults to false
boolean Prefix the day with its weekday — "Wed 17".
labels { day?: string; month?: string; year?: string } Accessible names for the three wheels.
onchange (value: Date) => void Fires once a wheel settles.
columnClasses PickerColumnClasses Per-part overrides for the wheels themselves.
classes PickerClasses size PickerSize Row height and type scale.
surface PickerSurface How the frame is drawn.
visible number Rows on screen at once. Rounded up to the next odd number.
title string A title above the wheels.
disabled boolean Disables every wheel inside.
haptics boolean Play a tick as each row passes the centre, once haptics are on.
onconfirm () => void Renders the action row. Called with the value already committed.
oncancel () => void Renders a cancel button beside it.
confirmLabel string cancelLabel string footer Snippet Replaces the built-in action row.
class string DateTimePicker
A day, an hour and a minute in one row of wheels.
The day is one wheel of whole dates — "Today", "Tomorrow", "Wed 17 Sep" — rather than three of day, month and year. It is the shape a datetime actually gets picked in: a booking is next Tuesday at half four, not the 17th and September and 2026.
The range is a year forward from today unless you say otherwise, so a picker for something in the past needs a min.
import { DateTimePicker } from 'omaris' <DateTimePicker bind:value={startsAt} minuteStep={15} /> Parts
Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.
root- No description in the source yet.
header- The title line above the wheels.
stage- Everything the band is drawn behind: the wheels and both fades.
band- The strip marking the centre row.
wheels- The row of wheels. Masked so rows dissolve towards the edges.
actions- The cancel/confirm row. Only rendered when
onconfirmis given. action- One of those buttons.
Props
value bindableDefaults to null
Date | null The moment. Bindable.
min Date First selectable day. Defaults to today.
max Date Last selectable day. Defaults to a year from today.
minuteStep Defaults to 5
number Minutes between rows.
locale string BCP-47 tag for the day labels.
relative Defaults to true
boolean Say "Today" and "Tomorrow" instead of their dates.
labels { day?: string; hour?: string; minute?: string; meridiem?: string; am?: string; pm?: string; } onchange (value: Date) => void columnClasses PickerColumnClasses Per-part overrides for the wheels themselves.
classes PickerClasses size PickerSize Row height and type scale.
surface PickerSurface How the frame is drawn.
visible number Rows on screen at once. Rounded up to the next odd number.
title string A title above the wheels.
disabled boolean Disables every wheel inside.
haptics boolean Play a tick as each row passes the centre, once haptics are on.
onconfirm () => void Renders the action row. Called with the value already committed.
oncancel () => void Renders a cancel button beside it.
confirmLabel string cancelLabel string footer Snippet Replaces the built-in action row.
class string NumberPicker
One wheel over a run of numbers.
A hundred values in one flick, which is what a wheel is for. On a desktop, where a typed number is faster, Number Input is the better control.
import { NumberPicker } from 'omaris' <NumberPicker bind:value={weight} min={30} max={200} unit="kg" /> Parts
Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.
root- No description in the source yet.
header- The title line above the wheels.
stage- Everything the band is drawn behind: the wheels and both fades.
band- The strip marking the centre row.
wheels- The row of wheels. Masked so rows dissolve towards the edges.
actions- The cancel/confirm row. Only rendered when
onconfirmis given. action- One of those buttons.
Props
value bindableDefaults to 0
number The number. Bindable.
min Defaults to 0
number max Defaults to 100
number step Defaults to 1
number The gap between rows.
precision Defaults to 0
number Decimal places on each row's label.
unit string Drawn small beside every row — "kg", "%".
loop Defaults to false
boolean Spin past the ends and come back round.
label Defaults to 'Value'
string Accessible name for the wheel.
format (value: number) => string Replaces the row's text.
onchange (value: number) => void columnClasses PickerColumnClasses Per-part overrides for the wheel itself.
classes PickerClasses size PickerSize Row height and type scale.
surface PickerSurface How the frame is drawn.
visible number Rows on screen at once. Rounded up to the next odd number.
title string A title above the wheels.
disabled boolean Disables every wheel inside.
haptics boolean Play a tick as each row passes the centre, once haptics are on.
onconfirm () => void Renders the action row. Called with the value already committed.
oncancel () => void Renders a cancel button beside it.
confirmLabel string cancelLabel string footer Snippet Replaces the built-in action row.
class string PickerColumn
One wheel.
The scrolling is the browser's own — overflow-y: scroll with scroll-snap-type: y mandatory — because nothing written in JavaScript matches the momentum a phone gives a native scroller for free. What is scripted is only the part CSS cannot do: the cylinder, where each row is tilted and faded by its distance from the centre, and settling on the row that ends up there.
import { PickerColumn } from 'omaris' <PickerColumn label="Hour" options={hours} bind:value={hour} loop /> Parts
Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.
root- The scroller. Holds the snap points and the perspective.
item- One snap target. Never transformed — a scaled row moves its own snap point.
face- The row's face. This is what tilts, fades and scales.
unit- The unit glued to every row — "kg", "min".
Props
options required readonly (PickerOption<T> | string)[] The rows. Strings are shorthand for { value: s, label: s }.
value bindableDefaults to undefined as T
T The selected row's value. Bindable.
label string Accessible name for the wheel — "Hour", "Month". Required in practice.
caption string A caption above the wheel. Rarely needed; the value usually says it.
loop Defaults to false
boolean Spin past the ends and come back round. For a cycle — hours, minutes, months — and wrong for a bounded range like a year.
unit string A unit drawn small beside every row — "kg", "min".
size PickerSize Overrides the size inherited from the Picker.
smmdlg
visible number Overrides the visible row count inherited from the Picker.
disabled Defaults to false
boolean hug Defaults to false
boolean Let the wheel size itself to its widest row instead of sharing the width.
onchange (value: T) => void Fires once the wheel settles, not on every row it passes.
row Snippet<[PickerOption<T>, boolean]> Replaces the row's text. Gets the option and whether it is centred.
class string classes PickerColumnClasses Per-part Tailwind overrides. class still covers the root.
TimePicker
Hour, minute and AM/PM.
Always twelve-hour with AM/PM — omaris never shows a 24-hour clock. The value is still stored 24-hour — "14:30" — so it sorts, compares and goes into a database without a second format to agree on; that is data, and the wheels are what a person reads.
import { TimePicker } from 'omaris' <TimePicker bind:value={opensAt} minuteStep={15} /> Parts
Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.
root- No description in the source yet.
header- The title line above the wheels.
stage- Everything the band is drawn behind: the wheels and both fades.
band- The strip marking the centre row.
wheels- The row of wheels. Masked so rows dissolve towards the edges.
actions- The cancel/confirm row. Only rendered when
onconfirmis given. action- One of those buttons.
Props
value bindableDefaults to '09:00'
string "HH:mm", stored 24-hour; the wheels always show 1–12 and AM/PM. Bindable.
minuteStep Defaults to 1
number Minutes between rows — 5 or 15 for an opening time, 1 for an alarm.
loop Defaults to true
boolean Spin past midnight and noon instead of stopping. On by default.
labels { hour?: string; minute?: string; meridiem?: string; am?: string; pm?: string } Accessible names for the wheels, and the AM/PM row text.
onchange (value: string) => void Fires once a wheel settles, with the 24-hour string.
columnClasses PickerColumnClasses Per-part overrides for the wheels themselves.
classes PickerClasses size PickerSize Row height and type scale.
surface PickerSurface How the frame is drawn.
visible number Rows on screen at once. Rounded up to the next odd number.
title string A title above the wheels.
disabled boolean Disables every wheel inside.
haptics boolean Play a tick as each row passes the centre, once haptics are on.
onconfirm () => void Renders the action row. Called with the value already committed.
oncancel () => void Renders a cancel button beside it.
confirmLabel string cancelLabel string footer Snippet Replaces the built-in action row.
class string