<!-- Generated by tools/docs.php from docs/src/form.html - edit the source, not this file. -->

# Form fields

Text fields, selects and text areas with Bootstrap 5's names: a label floating in the field or above it, a hint, an error said in words, an icon inside the field.

## Examples

`form-floating` keeps the label inside the field and lifts it when the field has a value or the focus. The label follows the control, and the control needs a `placeholder` - it is how CSS knows the field is empty.

```html
<div class="form-floating">
	<input class="form-control" id="form-floating-name" value="North Bay: marine park by speedboat" placeholder="A name people search for">
	<label for="form-floating-name">Name</label>
</div>
<div class="form-floating">
	<input class="form-control" id="form-floating-mail" type="email" placeholder="name@example.com">
	<label for="form-floating-mail">E-mail</label>
</div>
<div class="form-floating">
	<select class="form-select" id="form-floating-section"><option>Marine</option><option>Land</option><option>Evening</option></select>
	<label for="form-floating-section">Section</label>
</div>
<div class="form-floating">
	<textarea class="form-control" id="form-floating-note" placeholder="A note for the guides">Pick up at the pier at 8:30.</textarea>
	<label for="form-floating-note">Note</label>
</div>
```

### A label above, a hint, an error

`form-field` stacks a field's parts: `form-label`, the control, `form-text` for a hint and `invalid-feedback` for an error. The control points at both with `aria-describedby`; an error sets `aria-invalid="true"`, and the browser's own `:user-invalid` draws the same after the person has edited the field.

```html
<div class="form-field">
	<label class="form-label" for="form-stacked-slug">Address</label>
	<input class="form-control font-monospace" id="form-stacked-slug" value="north-bay" aria-describedby="form-stacked-slug-hint">
	<div class="form-text" id="form-stacked-slug-hint">Full address /excursions/north-bay/. The old one answers with a 301 redirect.</div>
</div>
<div class="form-field">
	<label class="form-label" for="form-stacked-price">Adult price, THB</label>
	<input class="form-control tabular-nums" id="form-stacked-price" type="number" value="2500" min="0" step="100" aria-invalid="true" aria-describedby="form-stacked-price-error">
	<div class="invalid-feedback" id="form-stacked-price-error"><i class="bi bi-exclamation-triangle-fill" aria-hidden="true"></i>Lower than the child price</div>
</div>
```

### A longer hint

A `details` with `form-more` folds what a short hint cannot say. Its chevron turns as it opens.

```html
<div class="form-field">
	<label class="form-label" for="form-more-keep">Keep the log for, days</label>
	<input class="form-control tabular-nums" id="form-more-keep" type="number" value="90" min="1" aria-describedby="form-more-hint">
	<div class="form-text" id="form-more-hint">Older records are removed at night.</div>
	<details class="form-more form-text"><summary><i class="bi bi-chevron-right" aria-hidden="true"></i>More</summary><div class="mt-4">The count starts from the day a record is written. A record a person marked to keep stays, whatever this number says, until the mark is taken off.</div></details>
</div>
```

### An icon in the field

`form-adorn` puts an icon inside a field or a select, at its start; the field keeps its own name. With `form-floating` on the same element the label starts after the icon, inside the one border - the way a sign-in form draws its fields.

```html
<div class="form-adorn">
	<i class="bi bi-search" aria-hidden="true"></i>
	<input class="form-control" type="search" placeholder="Name, slug or code" aria-label="Search excursions">
</div>
<div class="form-floating form-adorn">
	<i class="bi bi-envelope" aria-hidden="true"></i>
	<input class="form-control" id="form-adorn-mail" type="email" placeholder="name@example.com" autocomplete="email">
	<label for="form-adorn-mail">E-mail</label>
</div>
<div class="form-floating form-adorn">
	<i class="bi bi-globe2" aria-hidden="true"></i>
	<select class="form-select" id="form-adorn-country" autocomplete="country-name"><option>Northland</option><option>Southland</option><option>Eastland</option></select>
	<label for="form-adorn-country">Country</label>
</div>
<div class="input-group">
	<div class="form-floating form-adorn">
		<i class="bi bi-key" aria-hidden="true"></i>
		<input class="form-control" id="form-adorn-password" type="password" placeholder="Password" autocomplete="current-password">
		<label for="form-adorn-password">Password</label>
	</div>
	<button class="btn btn-icon" type="button" commandfor="form-adorn-password" command="--reveal" aria-pressed="false" aria-label="Show the password"><i class="bi bi-eye" aria-hidden="true"></i></button>
</div>
```

### Sizes

`form-control-sm` and `form-select-sm` are 28 px high, the default 32 px, `-lg` 40 px - the same heights as the buttons beside them.

```html
<input class="form-control form-control-sm" placeholder="Small" aria-label="Small field">
<input class="form-control" placeholder="Default" aria-label="Default field">
<input class="form-control form-control-lg" placeholder="Large" aria-label="Large field">
<select class="form-select form-select-sm" aria-label="Small select"><option>Small select</option></select>
```

### A one-time code

`form-control-otp` spaces the digits of a code from an app or a message in one field. One field takes a paste and the code the system offers above the keyboard - a box per digit takes neither - and the form goes by its button, never by the last digit.

```html
<div class="form-field">
	<label class="form-label" for="form-otp-code">6-digit code from your app</label>
	<input class="form-control form-control-otp" id="form-otp-code" type="text" inputmode="numeric" autocomplete="one-time-code" pattern="[0-9 \-]*" spellcheck="false" enterkeyhint="done" aria-describedby="form-otp-code-hint">
	<div class="form-text" id="form-otp-code-hint">Spaces and dashes are fine.</div>
</div>
```

### A new password

A button with `command="--reveal"` for a password field shows its text and hides it again - `aria-pressed` says which - and the field hides it on its form's submit and when the page comes back from the history. Each element with `data-strength` naming the field shows its level as it is typed: shorter than its `minlength`, long enough, then longer or of three kinds of characters, then both - a `meter` at once, and the words, the page's texts `strength.short`, `.fair`, `.good` and `.strong`, once the typing pauses, said politely by a live region of the field's own - VoiceOver says nothing of a password field's own description changing, so the words' element needs no `aria-live`. The level is a hint: the server keeps its own rules.

```html
<div class="form-field">
	<label class="form-label" for="form-password-new">New password</label>
	<div class="input-group">
		<input class="form-control" id="form-password-new" type="password" autocomplete="new-password" minlength="12" spellcheck="false" aria-describedby="form-password-hint form-password-level">
		<button class="btn btn-icon" type="button" commandfor="form-password-new" command="--reveal" aria-pressed="false" aria-label="Show the password"><i class="bi bi-eye" aria-hidden="true"></i></button>
	</div>
	<meter class="meter" min="0" max="4" low="2" high="3" optimum="4" value="0" data-strength="form-password-new" aria-label="Password strength"></meter>
	<div class="form-text" id="form-password-level" data-strength="form-password-new"></div>
	<div class="form-text" id="form-password-hint">At least 12 characters. A phrase of a few words is easy to remember.</div>
</div>
```

### A range

`form-range` on an `<input type="range">` is the browser's own slider in the theme's accent: it keeps its keys (the arrows, `Home`, `End`, `Page Up` and `Page Down`) and its look in forced colors. The label says what it sets; when the number matters, write it beside the slider.

```html
<div class="form-field">
	<label class="form-label" for="form-range-quality">Photo quality</label>
	<input class="form-range" id="form-range-quality" type="range" min="40" max="100" step="5" value="80">
</div>
```

### A button beside a floating field

`btn-float` makes a button as tall as a floating field, for the one action beside it; with `btn-icon` it is square.

```html
<div class="hstack gap-8">
	<div class="form-floating flex-fill">
		<input class="form-control" id="form-float-button-code" placeholder="Voucher code">
		<label for="form-float-button-code">Voucher code</label>
	</div>
	<button class="btn btn-secondary btn-float" type="button">Apply</button>
</div>
```

### A text that grows

`data-grow` on a `textarea` starts it at its `rows` - `rows="1"` is one line - and grows it with its text, without the five lines a text area takes otherwise. Where the browser has `field-sizing: content` - Chrome 123, Firefox 152, Safari 26.2 - CSS does it; below, the library sizes it from its text as it is typed, reset and loaded. A `max-h-*` caps it, and past the cap it scrolls.

```html
<div class="form-field">
	<label class="form-label" for="form-grow-text">Translation</label>
	<textarea class="form-control max-h-160" id="form-grow-text" rows="1" data-grow>Speedboat to the marine park</textarea>
</div>
```

### A count of characters

`form-counter` with `data-counter` naming a field shows how much is typed against its `maxlength`, or against the counter's `data-counter-max`, which lets the text go over - a title the search results cut. From nine tenths it turns to the warning tone, past the most to danger (`data-state` `near` and `over`), and once the typing pauses near or over, the count is said politely in the page's text `counter`. Joined to the field by `aria-describedby`, the count is read with it.

```html
<div class="form-field">
	<label class="form-label" for="form-counter-title">Page title</label>
	<input class="form-control" id="form-counter-title" value="North Bay marine park by speedboat" aria-describedby="form-counter-title-count">
	<div class="form-counter" id="form-counter-title-count" data-counter="form-counter-title" data-counter-max="60"></div>
</div>
```

### Number steps

Buttons with `command="--step-up"` or `"--step-down"` and `aria-controls` naming a number field step it by its `step` within its `min` and `max`, and fire `input` and `change` as typing does; at an end a button is `aria-disabled`. The arrow keys in the field step it already: the buttons are for a pointer.

```html
<div class="form-field">
	<label class="form-label" for="form-step-guests">Guests</label>
	<div class="input-group max-w-160">
		<button class="btn btn-icon" type="button" command="--step-down" aria-controls="form-step-guests" aria-label="One guest fewer"><i class="bi bi-dash-lg" aria-hidden="true"></i></button>
		<input class="form-control tabular-nums text-center" id="form-step-guests" type="number" min="1" max="12" value="2">
		<button class="btn btn-icon" type="button" command="--step-up" aria-controls="form-step-guests" aria-label="One guest more"><i class="bi bi-plus-lg" aria-hidden="true"></i></button>
	</div>
</div>
```

### A colour

`form-control-color` is the browser's own colour picker in a field's box. A text field with `data-color-for` naming the picker is its hex twin: each follows the other, and a twin that holds no colour - `#rgb` or `#rrggbb`, the `#` optional - is `aria-invalid`. A stored colour is drawn by `data-color`, which the library sets as `--redz-color` through the CSSOM, so a page under a strict CSP writes no style: on a `dot`, an `icon-tile` - its icon black or white by the colour's lightness -, a `choice-media`, and the edges `edge-top-color` and `edge-start-color`.

```html
<div class="form-field">
	<label class="form-label" for="form-color-hex">Label colour</label>
	<div class="input-group max-w-240">
		<input class="form-control form-control-color" id="form-color-pick" type="color" value="#2a9d8f" aria-label="Pick the label colour">
		<input class="form-control font-monospace" id="form-color-hex" data-color-for="form-color-pick" value="#2a9d8f" spellcheck="false" autocomplete="off">
	</div>
</div>
<div class="hstack gap-12">
	<span class="dot" data-color="#2a9d8f"></span>
	<span class="icon-tile" data-color="#e9c46a"><i class="bi bi-tag" aria-hidden="true"></i></span>
	<span class="icon-tile" data-color="#264653"><i class="bi bi-tag" aria-hidden="true"></i></span>
	<div class="card edge-start-color" data-color="#e76f51"><div class="card-body">Overdue</div></div>
</div>
```

### Sent on a change

`data-submit="change"` on a form or on one of its controls sends the form when such a control changes - a filter's select, a sort, a switch - by `requestSubmit`, so the browser's checks and the page's submit listeners run. A text field changes when it is left, not at every key; a combobox's list when a value is chosen.

```html
<form class="hstack flex-wrap gap-12" action="#form-submit" data-submit="change" data-docs-submit>
	<select class="form-select max-w-240" name="sort" aria-label="Sort by"><option value="new">Newest first</option><option value="price">Lowest price first</option></select>
	<label class="form-check"><input class="check" type="checkbox" name="open"> Open now</label>
	<output class="text-muted" data-docs-sent></output>
</form>
```

### Shown by another field

`data-show-when` on a region names a field of its form, and `data-show-in` the values that show it, a space between them; without it, any value does. A checkbox or a radio counts its value when checked. A hidden field beside a checkbox of the same name is what the form sends while it is off: `data-show-in` can name its value, and a region without `data-show-in` does not count it. While the region is hidden its fields are disabled, so the form sends none of them; a field the page disabled itself stays disabled. A region inside a hidden region counts as hidden, whatever its own field holds.

```html
<form class="vstack gap-12">
	<div class="form-floating max-w-320">
		<select class="form-select" id="form-show-pay" name="pay"><option value="cash">Cash on the day</option><option value="card">Card</option><option value="invoice">Invoice</option></select>
		<label for="form-show-pay">Payment</label>
	</div>
	<div class="form-floating max-w-320" id="form-show-tax-region" data-show-when="pay" data-show-in="invoice">
		<input class="form-control" id="form-show-tax" name="tax_id" placeholder="Tax ID">
		<label for="form-show-tax">Tax ID</label>
	</div>
	<div class="vstack gap-4">
		<input type="hidden" name="remind" value="0">
		<label class="form-check"><input class="switch" type="checkbox" role="switch" name="remind" value="1"> Remind the guest</label>
		<div class="form-text" id="form-show-on" data-show-when="remind">A message goes out the evening before.</div>
		<div class="form-text" id="form-show-off" data-show-when="remind" data-show-in="0">The guest is told nothing.</div>
	</div>
</form>
```

## States

## Keyboard and accessibility

- Every control has a visible label: a `<label for>`, or `aria-label` only where the context says it all - a search field with its icon.
- A placeholder is never the label: it goes away as the person types. In `form-floating` the placeholder is only the empty marker.
- An error is said in words in `invalid-feedback`, joined to the control by `aria-describedby`; a color alone never tells it.
- The borders of fields and the focus ring are at least 3:1 against the page in both themes; the text 4.5:1.
- `form-select` paints its own chevron from the theme, mirrored right to left; a multiple select or one with `size` drops it.
- A count of characters is said only near or over its most, once the typing pauses; at every key it would drown the typing.
- Number step buttons are named by `aria-label` and keep their place in the Tab order; from the keyboard the field's own arrow keys step it.
- A region shown by another field is `hidden` while it does not apply, so a screen reader skips it and the form sends nothing of it.

## Tokens

From [`css/components/form.css`](https://github.com/redz-tech/REDZ.UI/blob/main/css/components/form.css), which declares no custom property of its own.

It reads the theme's `--redz-space-4`, `--redz-text`, `--redz-fs-12`, `--redz-text-muted`, `--redz-link`, `--redz-motion`, `--redz-ease`, `--redz-danger`, `--redz-control-height`, `--redz-space-12`, `--redz-border-strong`, `--redz-radius-8`, `--redz-bg`, `--redz-focus`, `--redz-space-8`, `--redz-grow-rows`, `--redz-form-select-chevron`, `--redz-warning`, `--redz-radius-6`, `--redz-bg-page`, `--redz-primary-subtle`, `--redz-control-height-sm`, `--redz-control-height-lg`, `--redz-fs-16`, `--redz-control-height-float`, `--redz-fs-14`, `--redz-primary`.

## From Bootstrap 5.3

| Bootstrap 5.3 | REDZ.UI | Change | Note |
| --- | --- | --- | --- |
| `col-form-label` | `form-label` | review | a form-label in a form-field |
| `form-control-plaintext` | - | review | readonly on a form-control, or plain text |
| `has-validation` | - | remove | fields have no success state |
| `invalid-tooltip` | `invalid-feedback` | review | the error under the field |
| `is-invalid` | - | state | aria-invalid="true" on the control |
| `is-valid` | - | remove | fields have no success state |
| `valid-feedback` | - | remove | fields have no success state |
| `valid-tooltip` | - | remove | fields have no success state |
| `was-validated` | - | state | the browser's :user-invalid shows after the person edits |
