# Select

> A native select, styled to match the rest of the inputs, with options built from data.

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.

```javascript
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.

```javascript
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.

```javascript
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>`.

```javascript
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

```javascript
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

```javascript
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.

```javascript
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

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `options` | `SelectOption[]` | `[]` | Strings, { value, label, disabled } objects, or { label, options } for a group. |
| `value` | `string \| number` | — | Which option is selected. |
| `placeholder` | `string` | — | Disabled first option, selected when there is no value. |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Control height and text size. |
| `name` | `string` | — | Form field name; the id is derived from it. |
| `invalid` | `boolean` | `false` | Sets aria-invalid. selectField sets this for you from error. |
| `disabled` | `boolean` | `false` | Disables 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.
