Input
Use input with the current published Askr packages.
@askrjs/ui/input0.0.13@askrjs/themes/input0.0.13
How to use input
Input should use the standard themed surface unless the application is deliberately implementing its own design system on top of @askrjs/ui.
- Import the themed component from @askrjs/themes/components; use @askrjs/ui directly only when building a custom visual system.
- Keep input labels, state, and application actions visible at the call site instead of styling internal DOM nodes.
- Verify keyboard behavior, focus movement or return, disabled state, and both standard themes.
import { Button, Field, FieldHint, FieldLabel, Input } from '@askrjs/themes/components';
<Field>
<FieldLabel for="project-name">Project name</FieldLabel>
<Input id="project-name" name="name" required />
<FieldHint>Shown to everyone in the workspace.</FieldHint>
<Button type="submit">Save project</Button>
</Field>Purpose
Input is a thin headless wrapper around the native <input> element — it doesn't add its own value management, validation, or masking, it just forwards disabled and tabIndex handling consistently and supports asChild for polymorphic rendering. DebouncedInput builds on top of it for the common search-box case: it still renders a real input, but adds a settled onDebouncedInput callback so you're not re-running an expensive filter or request on every keystroke. Reach for plain Input for ordinary form fields, and DebouncedInput specifically when the value feeds a search, filter, or autocomplete request.
Install and import
Two packages cover Input: @askrjs/ui plus @askrjs/askr for the headless behavior, and @askrjs/themes if you also want default styling applied. `Input` and `DebouncedInput` live at `@askrjs/ui/input` in headless form, or you can pull `Input` from `@askrjs/themes/input` to get the same component wired to theme CSS. Since themes re-exports the @askrjs/ui Input rather than layering a separate wrapper around it, props and behavior stay identical no matter which path you import from.
Live examples
Look at a plain text input alongside a disabled one to see the visual and focus differences, then an asChild example wrapping a custom element to confirm props merge onto it rather than a native input being added underneath. A DebouncedInput example wired to a search list is the best way to feel the difference between its onInput (fires on every change) and onDebouncedInput (fires once typing settles).
Anatomy
Input renders a single native <input> and nothing else — no wrapping div, no icon slot, no label. If you need an icon or affix alongside the field, that's what InputGroup (from Form, Label, Field, and Input Group) is for; Input itself stays a bare element so it can sit inside whatever layout you build around it. With asChild, Input's props are merged onto the single child you provide instead of rendering its own element.
State model
Input passes through native value/onInput/checked-style props untouched, so whether it's controlled or uncontrolled is entirely up to how you use those native attributes — Input adds no state of its own beyond disabled and tabIndex. DebouncedInput is different: it tracks the debounce timer internally and exposes onDebouncedInput(value: string) after debounceMs of inactivity, in addition to (not instead of) forwarding the raw onInput event with type InputEvent.
Keyboard and accessibility
Because Input is a real <input>, it inherits full native keyboard and screen-reader behavior with no extra ARIA wiring required from the component. It supports labeling via a <label for>, aria-label, or aria-labelledby (all three are declared supported in INPUT_A11Y_CONTRACT), defaults to tabIndex 0, and drops to tabIndex -1 when disabled is set.
Styling and tokens
Themed inputs target [data-slot="input"] in the default stylesheet, which sets border, radius, and focus-ring tokens like --ak-color-input and --ak-color-ring, and reads --ak-density-control-height-md for its height. An aria-invalid="true" attribute automatically swaps the border to --ak-color-danger, so validation styling is a matter of setting aria-invalid on the input rather than passing a variant prop — Input has no variant prop of its own.
API
InputInputProps extends the native <input> attributes (minus children and ref) with disabled?: boolean and tabIndex?: number, plus asChild?: false and a typed ref to HTMLInputElement; InputAsChildProps drops the native attributes in favor of asChild: true and a single required child. DebouncedInputProps is built from InputInputProps with onInput and type overridden: type stays open to any native input type, debounceMs?: number sets the wait, and onDebouncedInput?: (value: string) => void fires the settled value.
Edge cases
With DebouncedInput, an onInput handler you pass still fires on every keystroke — only onDebouncedInput waits for debounceMs, so if you only need the settled value, wire your logic to onDebouncedInput and leave onInput alone to avoid duplicate work. Because asChild requires a single child and forwards every Input prop onto it, wrapping something that isn't a real input-like element (no value/onInput support) will silently drop the interaction props with nowhere to go.
Related pages
See Textarea for the multi-line equivalent, Button and Button Group for the other native-element headless primitive with the same asChild pattern, and Form, Label, Field, and Input Group for composing Input with labels, affixes, and validation messaging.