Select

This is a real <select> with the browser’s own dropdown — which means it works with no JavaScript, opens correctly on a phone, and is keyboard-navigable without anything from this library.

select() is the bare control; selectField() wraps it in a label, help text and error message, the same way textField() does.

Basic select

Options can be plain strings, in which case the value and the label are the same.

selectField({
  label: 'Theme',
  name: 'theme',
  options: ['Light', 'Dark', 'System'],
})

Values and labels

Pass objects when the value that gets submitted differs from the text a person reads. value marks the selected option.

selectField({
  label: 'Output',
  name: 'output',
  value: 'dist',
  options: [
    { value: 'dist', label: 'dist/ — the default' },
    { value: 'build', label: 'build/' },
    { value: 'public', label: 'public/', disabled: true },
  ],
})

Placeholder

A placeholder renders as a disabled first option, selected when value is absent — so the field starts empty without being a valid choice.

selectField({
  label: 'Deploy target',
  name: 'target',
  placeholder: 'Choose a host…',
  options: ['Netlify', 'Vercel', 'Cloudflare Pages', 'GitHub Pages'],
})

Groups

An entry with its own options array becomes an <optgroup>.

selectField({
  label: 'Page extension',
  name: 'ext',
  value: '.ht.js',
  options: [
    { label: 'JavaScript', options: ['.ht.js', '.html.js'] },
    { label: 'TypeScript', options: ['.ht.ts', '.html.ts'] },
    { label: 'JSX', options: ['.ht.jsx', '.ht.tsx'] },
  ],
})

Sizes

stack({ gap: 'md' },
  selectField({ label: 'Small', name: 'sm', size: 'sm', options: ['One', 'Two'] }),
  selectField({ label: 'Medium', name: 'md', size: 'md', options: ['One', 'Two'] }),
  selectField({ label: 'Large', name: 'lg', size: 'lg', options: ['One', 'Two'] }),
)

Help, error and disabled

Used for the html lang attribute.
Choose a framework to continue.
stack({ gap: 'lg' },
  selectField({
    label: 'Locale',
    name: 'locale',
    options: ['en', 'es', 'fr'],
    help: 'Used for the html lang attribute.',
  }),
  selectField({
    label: 'Framework',
    name: 'framework',
    placeholder: 'Pick one…',
    options: ['sitelo'],
    error: 'Choose a framework to continue.',
  }),
  selectField({
    label: 'Plan',
    name: 'plan',
    options: ['Free'],
    disabled: true,
  }),
)

From data

Options are just an array, so they usually come from whatever data() already loaded for the page.

return (() => {
  const posts = [
    { slug: 'hello-world', title: 'Hello world' },
    { slug: 'static-first', title: 'Static first' },
    { slug: 'no-runtime', title: 'No runtime' },
  ]

  return selectField({
    label: 'Featured post',
    name: 'featured',
    value: 'static-first',
    options: posts.map((post) => ({ value: post.slug, label: post.title })),
  })
})()

Props

PropTypeDefaultDescription
optionsSelectOption[][]Strings, { value, label, disabled } objects, or { label, options } for a group.
valuestring | numberWhich option is selected.
placeholderstringDisabled first option, selected when there is no value.
size'sm' | 'md' | 'lg''md'Control height and text size.
namestringForm field name; the id is derived from it.
invalidbooleanfalseSets aria-invalid. selectField sets this for you from error.
disabledbooleanfalseDisables the control.

selectField() additionally takes label, help, error, required and fieldClass — see textField(). Children are appended after the generated options, so you can hand-write any you need.