Skip to main content
On this pageOverview

Input

Overview

An accessible text input that links a label and description to the input element via ARIA attributes. Input is a stateless render helper: call it directly with a ViewConfig in your own view; no Model, update, or h.submodel wrapping. It provides three attribute groups (input, label, and description) that you spread onto your own elements to get correct accessibility wiring.

See it in an app

Check out how Input is wired up in a real Foldkit app.

Examples

Basic

Pass an id, an onInput handler, and a toView callback. The callback receives attribute groups for three elements: label (linked via for), input (with ARIA attributes), and description (linked via aria-describedby).

As it appears on your government-issued ID.
// Pseudocode — Input is view-only. The value lives in your own Model as a
// string. Replace model.name and UpdatedName with your own field and Message.
import type { HtmlBuilder } from 'foldkit/html'

import { Input } from '@foldkit/ui'

const view = (model: Model, h: HtmlBuilder<Message>) =>
  Input.view(
    {
      id: 'full-name',
      value: model.name, // your Model field
      onInput: value => UpdatedName({ value }), // your Message
      placeholder: 'Enter your full name',
      toView: attributes =>
        h.div(
          [h.Class('flex flex-col gap-1.5')],
          [
            h.label(
              [...attributes.label, h.Class('text-sm font-medium')],
              ['Name'],
            ),
            h.input([
              ...attributes.input,
              h.Class('w-full rounded-lg border border-gray-300 px-3 py-2'),
            ]),
            h.span(
              [...attributes.description, h.Class('text-sm text-gray-500')],
              ['As it appears on your government-issued ID.'],
            ),
          ],
        ),
    },
    h,
  )

Disabled

Set isDisabled: true to disable the input. Unlike Button, Input uses the native disabled attribute, so the browser prevents interaction entirely.

This input is disabled.
// Pseudocode — Input is view-only. Disabled inputs display a fixed value
// and ignore onInput events.
import type { HtmlBuilder } from 'foldkit/html'

import { Input } from '@foldkit/ui'

const view = (h: HtmlBuilder<Message>) =>
  Input.view(
    {
      id: 'email-disabled',
      isDisabled: true,
      value: 'ada@lovelace.dev',
      toView: attributes =>
        h.div(
          [h.Class('flex flex-col gap-1.5')],
          [
            h.label(
              [...attributes.label, h.Class('text-sm font-medium')],
              ['Email'],
            ),
            h.input([
              ...attributes.input,
              h.Class(
                'w-full rounded-lg border px-3 py-2 data-[disabled]:opacity-50',
              ),
            ]),
            h.span(
              [...attributes.description, h.Class('text-sm text-gray-500')],
              ['Contact your admin to update.'],
            ),
          ],
        ),
    },
    h,
  )

Styling

Input is headless. Your toView callback controls all markup and styling. Use the data attributes below to style disabled, read-only, and invalid states. For validation, set isInvalid: true and style with data-[invalid] in your CSS.

AttributeCondition
data-disabledPresent when isDisabled is true.
data-readonlyPresent when isReadOnly is true.
data-invalidPresent when isInvalid is true.

Keyboard Interaction

Input uses the native <input> element, so all keyboard interaction is handled by the browser.

KeyDescription
TabMoves focus to or away from the input.

A read-only input still takes focus and allows selection and copying. Typing does not change the value.

Accessibility

The three attribute groups wire up ARIA relationships automatically. The label group includes for pointing to the input id. The description group includes an id that the input references via aria-describedby. You can access this description ID directly with Input.descriptionId(id) if you need to reference it outside the toView callback.

When isInvalid is true, aria-invalid="true" is set on the input element so screen readers announce the error state.

isReadOnly sets the native readonly attribute, so the browser exposes the read-only state to assistive technology without any extra ARIA. The value stays focusable, selectable, and copyable, and the field is still submitted with its form.

isDisabled sets the native disabled attribute instead. A disabled input is not focusable and is left out of form submission. Use isReadOnly when the value still matters to the user and only editing is blocked, and isDisabled when the field is unavailable.

The two flags are independent. Setting both emits both attribute sets, and either one on its own removes the input handler. Browsers give disabled precedence when both are present.

API Reference

ViewConfig

Configuration object passed to Input.view().

NameTypeDefaultDescription
idstringUnique ID for the input element. Used to link the label and description via ARIA attributes.
toView(attributes: InputAttributes) => HtmlCallback that receives attribute groups for the input, label, and description elements.
onInput((value: string) => Message) | undefinedOptional function that maps the current input value to a Message on each input event. Omit for a read-only display.
valuestringThe current value of the input.
isDisabledbooleanfalseWhether the input is disabled. Sets the native disabled attribute.
isReadOnlybooleanfalseWhether the input is readable but not editable. Sets the native readonly attribute and adds a data-readonly attribute for styling. Independent of isDisabled.
isInvalidbooleanfalseWhether the input is in an invalid state. Sets aria-invalid and adds a data-invalid attribute for styling.
isAutofocusbooleanfalseWhether the input receives focus when the page loads.
namestringThe form field name for native form submission.
typestring'text'The HTML input type (text, email, password, number, etc.).
placeholderstringPlaceholder text shown when the input is empty.

InputAttributes

Attribute groups provided to the toView callback.

NameTypeDefaultDescription
inputReadonlyArray<Attribute<Message>>Spread onto the <input> element. Includes id, type, value, ARIA attributes, and event handlers.
labelReadonlyArray<Attribute<Message>>Spread onto the <label> element. Includes a for attribute linking to the input id.
descriptionReadonlyArray<Attribute<Message>>Spread onto a description element. Includes an id that the input references via aria-describedby.

Stay in the update loop.

New releases, patterns, and the occasional deep dive.


Built with Foldkit.

© 2026 Devin Jameson