Input
A single-line native text input, and TextInput, the field with a label, description, message and counter.
import { Input, TextInput } from '@aksornproject/aksara-ui'Labelled field
TextInput renders a <label> linked to the input, then description, the input, message and counter, in that order. The label gets a generated id unless you pass id. required sets native required and draws a star after the label. The native input owns value, defaultValue, onChange and form submission, and ref is the HTMLInputElement.
<div className="w-full max-w-spacing-320">
<TextInput
label="Full name"
description="As it appears on your student card"
name="fullName"
placeholder="Somchai Jaidee"
required
/>
</div>Status and message
status takes 'error', 'warning' or 'success' and colours the border. message renders under the input in the status colour, with its glyph. 'error' also sets aria-invalid, unless you pass your own. destructive is a shorthand for status="error". Prefer status, which wins when you pass both.
<div className="flex w-full max-w-spacing-320 flex-col gap-spacing-16">
<TextInput
label="Email"
defaultValue="somchai@school"
status="error"
message="Enter a full email address."
/>
<TextInput
label="Username"
defaultValue="somchai"
status="warning"
message="This name is already common in your class."
/>
<TextInput
label="Classroom code"
defaultValue="M4-2"
status="success"
message="Classroom found."
/>
</div>Adornments
startAdornment and endAdornment render content in spans before and after the input. An adornment takes the placeholder colour while the input is empty and the primary text colour once it has a value. endAdornment is at most 80px wide. startAdornmentClassName and endAdornmentClassName reach the spans.
<div className="flex w-full max-w-spacing-320 flex-col gap-spacing-16">
<TextInput
label="Search lessons"
type="search"
placeholder="Search by title"
startAdornment={<IconInterfaceEssentialSearchLoupe size={20} />}
/>
<TextInput
label="Price"
type="number"
placeholder="0"
endAdornment="บาท"
/>
</div>Clear action and counter
clearAction renders in a 36px square slot before endAdornment. It is hidden while the input is disabled, read-only or loading. The component never clears the value itself, so the app owns the button's visibility, its accessible name, the value change and where focus goes. counter is app-owned text in a right-aligned row. It does not count anything, and maxLength passes through to the input unchanged.
<div className="w-full max-w-spacing-320">
<TextInput
ref={ref}
label="Course name"
value={value}
onChange={(event) => setValue(event.target.value)}
maxLength={100}
counter={`${value.length} / 100`}
clearAction={
value && (
<Button
type="button"
size="xs"
iconOnly
variant="tertiary-accent"
aria-label="Clear course name"
leadingIcon={<IconInterfaceEssentialDeleteDisabled />}
onClick={() => {
setValue('')
ref.current?.focus()
}}
/>
)
}
/>
</div>States
disabled keeps native disabled behaviour, so the value is left out of form data. readOnly uses a grey surface and keeps the text at full strength. loading keeps the input mounted with its value, sets readOnly and aria-busy, and covers the label and value with skeletons. Adornments, clearAction and message hide while loading. description and counter stay.
<div className="flex w-full max-w-spacing-320 flex-col gap-spacing-16">
<TextInput label="Disabled" defaultValue="Term 1, 2026" disabled />
<TextInput label="Read-only" defaultValue="STU-104238" readOnly />
<TextInput
label="Loading"
defaultValue="Somchai Jaidee"
description="Fetching your profile"
loading
/>
</div>Input
Input is the control without the field around it. Native props, events and ref go to the <input>. className and wrapperRef target the styled div around it, and containerShadowClassName targets the outer div. There are no size variants. size is the native attribute. Name a bare Input with aria-label or an InputLabel.
<Input
aria-label="Score"
type="number"
inputMode="numeric"
placeholder="0"
endAdornment="/ 100"
containerShadowClassName="w-spacing-160"
/>Accessibility
aria-describedbylists your own ids first, then the description, the message and the counter.- The required star is
aria-hidden. The input carries nativerequired. - No live region is added. Announce validation messages yourself if they need it.
- The focus ring draws when the input has keyboard focus. Hover and focus also switch the border to the accent colour, and a status border wins over both.
- Selectors must match attribute values. The input always has
data-disabledanddata-invalidas"true"or"false".
API
TextInput
TextInput takes every Input prop as well.
Prop
Type
Input
Prop
Type