Klarhimmel UI

Select

Stable

A custom dropdown with a keyboard-complete listbox, type-ahead and form value.

npx shadcn add @klarhimmel/select @klarhimmel/input

When to use

  • Use for

    Choosing one of five to fifty options, with search by typing the first letters.

  • Not for

    Two to five options. Use Radio group. Long lists people can name. Use Combobox.

  • Pairs with

    Field, forms.

Preview

Nothing chosen yet.

select.tsx
import { Theme } from '@/components/klarhimmel/theme/style';
import { Field } from '@/components/klarhimmel/ui/input';
import { Select } from '@/components/klarhimmel/ui/select';

export default function Example() {
  return (
    <Theme theme="brand">
      <Field label="Room">
        {(p) => (
          <Select
            {...p}
            options={[
              { value: 'sea', label: 'Seaside suite' },
              { value: 'garden', label: 'Garden room' },
              { value: 'loft', label: 'Loft' },
            ]}
            placeholder="Choose a room"
          />
        )}
      </Field>
    </Theme>
  );
}

Install

Import

import { Select } from '@/components/klarhimmel/ui/select';
Lands in
components/klarhimmel/ui/select.tsx, plus cn() and the theme item if they are missing. Import theme.css after Tailwind.
Render in
Render it inside <Theme>, once near the root of your app. Tokens and fonts are scoped to it, so outside it the component is unstyled.

Examples

  • States

    Choose a language to continue.

    Set by your country.

    import { Field } from '@/components/klarhimmel/ui/input';
    import { Select } from '@/components/klarhimmel/ui/select';
    
    const options = [
      { value: 'sv', label: 'Svenska' },
      { value: 'en', label: 'English' },
      { value: 'de', label: 'Deutsch' },
    ];
    
    export default function SelectStates() {
      return (
        <div className="grid w-[min(40rem,calc(100cqw-3rem))] min-h-60 content-start gap-6 @md:grid-cols-2">
          <Field label="Language" error="Choose a language to continue.">{(p) => <Select {...p} options={options} placeholder="Choose…" />}</Field>
          <Field label="Currency" hint="Set by your country.">{(p) => <Select {...p} options={[{ value: 'sek', label: 'SEK' }]} defaultValue="sek" disabled />}</Field>
        </div>
      );
    }
  • Form

    Not sent yet.

    import { useState } from 'react';
    import { Button } from '@/components/klarhimmel/ui/button';
    import { Field } from '@/components/klarhimmel/ui/input';
    import { Select } from '@/components/klarhimmel/ui/select';
    
    const options = [{ value: 'early', label: 'Early, 07:00' }, { value: 'normal', label: 'Normal, 09:00' }, { value: 'late', label: 'Late, 11:00' }];
    
    export default function SelectForm() {
      const [sent, setSent] = useState('');
      return (
        <form className="flex min-h-72 w-[min(24rem,calc(100cqw-3rem))] flex-col items-start gap-4" onSubmit={(e) => { e.preventDefault(); setSent(String(new FormData(e.currentTarget).get('breakfast') ?? '')); }}>
          <Field label="Breakfast time" className="w-full">{(p) => <Select {...p} name="breakfast" options={options} defaultValue="normal" />}</Field>
          <Button type="submit">Save</Button>
          <p role="status" className="text-kh-small text-kh-muted">{sent ? `The form sent breakfast=${sent}.` : 'Not sent yet.'}</p>
        </form>
      );
    }

API reference

PropTypeDefaultDescription
options{ value: string; label: string; disabled?: boolean }[]-Required.
value / defaultValue / onValueChangestring / (value: string) => void-Controlled or uncontrolled state.
placeholderstring'Choose…'Shown while nothing is chosen.
namestring-Submits the value with a form (a hidden input).
invalid / disabledbooleanfalseError styling; switches it off.
aria-label / aria-labelledbystring-Needed when no Field or <label htmlFor> names it.
classNamestring-Extra classes, merged after the component's own.

Accessibility

KeyDoes
Enter / Space / Down / UpOpens the list on the chosen option
Down / UpMoves the highlight (stops at the ends)
Home / EndFirst or last option
Page Down / Page UpTen options at a time
LettersJump to the option that starts with them; repeat a letter to cycle
Enter / SpaceChooses the highlighted option and closes
Alt+UpChooses and closes
TabChooses the highlighted option and moves on
EscapeCloses without changing

ARIA select-only combobox. A click outside only closes the list; Tab commits the highlighted option. Inside a small scrolling panel prefer NativeSelect.

Accessibility