Input
A text input component with label, helper text, error state, size variants,
clearable support, readonly/loading states, and full native form integration.
Basic
| 1 | <nana-input |
| 2 | label="Email address" |
| 3 | placeholder="you@example.com" |
| 4 | type="email" |
| 5 | helper-text="We'll never share your email." |
| 6 | ></nana-input> |
Types
| 1 | <nana-input label="Text" placeholder="Enter text" type="text"></nana-input> |
| 2 | <nana-input label="Email" placeholder="you@example.com" type="email"></nana-input> |
| 3 | <nana-input label="Password" placeholder="Enter password" type="password"></nana-input> |
| 4 | <nana-input label="Number" placeholder="42" type="number"></nana-input> |
| 5 | <nana-input label="Search" placeholder="Search..." type="search"></nana-input> |
| 6 | <nana-input label="Phone" placeholder="+1 (555) 000-0000" type="tel"></nana-input> |
| 7 | <nana-input label="Website" placeholder="https://example.com" type="url"></nana-input> |
Sizes
| 1 | <nana-input label="Extra Small" placeholder="xs" size="xs"></nana-input> |
| 2 | <nana-input label="Small" placeholder="sm" size="sm"></nana-input> |
| 3 | <nana-input label="Medium" placeholder="md" size="md"></nana-input> |
| 4 | <nana-input label="Large" placeholder="lg" size="lg"></nana-input> |
States
| 1 | <nana-input label="Normal" placeholder="Enter text"></nana-input> |
| 2 | <nana-input label="Disabled" placeholder="Disabled input" disabled></nana-input> |
| 3 | <nana-input label="Readonly" value="Read-only value" readonly></nana-input> |
| 4 | <nana-input label="Loading" placeholder="Checking..." loading></nana-input> |
| 5 | <nana-input label="Invalid" value="bad-value" invalid error-message="Please enter a valid email address"></nana-input> |
Prefix, Suffix & Clearable
| 1 | <!-- Prefix icon --> |
| 2 | <nana-input label="Search" placeholder="Search..."> |
| 3 | <nana-icon slot="prefix" name="search"></nana-icon> |
| 4 | </nana-input> |
| 5 | |
| 6 | <!-- Suffix icon --> |
| 7 | <nana-input label="Website" placeholder="https://example.com" type="url"> |
| 8 | <nana-icon slot="suffix" name="external-link"></nana-icon> |
| 9 | </nana-input> |
| 10 | |
| 11 | <!-- Clearable --> |
| 12 | <nana-input label="Username" placeholder="john_doe" clearable></nana-input> |
Validation
| 1 | <nana-input |
| 2 | label="Username" |
| 3 | placeholder="Choose a username" |
| 4 | helper-text="3–20 characters, letters and numbers only" |
| 5 | minlength="3" |
| 6 | maxlength="20" |
| 7 | ></nana-input> |
| 8 | |
| 9 | <nana-input |
| 10 | label="Confirm password" |
| 11 | type="password" |
| 12 | value="mismatch" |
| 13 | invalid |
| 14 | error-message="Passwords do not match" |
| 15 | ></nana-input> |
Events
| 1 | <nana-input id="my-input" label="Live value" placeholder="Type something..."></nana-input> |
| 2 | |
| 3 | <script> |
| 4 | const input = document.querySelector('#my-input'); |
| 5 | |
| 6 | // Fires on every keystroke |
| 7 | input.addEventListener('nana-input', (e) => console.log('input:', e.detail.value)); |
| 8 | |
| 9 | // Fires on blur or Enter (committed value) |
| 10 | input.addEventListener('nana-change', (e) => console.log('change:', e.detail.value)); |
| 11 | |
| 12 | // Fires when cleared via the × button or .clear() |
| 13 | input.addEventListener('nana-clear', () => console.log('cleared')); |
| 14 | </script> |
Customization
Use size for scale
and the boolean props for state. The field is built on theme tokens, so it follows light/dark
automatically; to match a brand, three CSS variables cover colour and radius, and the parts
handle anything finer.
| 1 | /* Theming hooks */ |
| 2 | .search-input { |
| 3 | --nana-input-bg: #0b1220; |
| 4 | --nana-input-color: #e5edff; |
| 5 | --nana-input-radius: 9999px; |
| 6 | } |
| 7 | |
| 8 | /* Or reach into a part for anything else */ |
| 9 | .search-input::part(input) { |
| 10 | font-variant-numeric: tabular-nums; |
| 11 | } |
| CSS Variable | Description |
--nana-input-bg | Field background colour |
--nana-input-color | Input text colour |
--nana-input-radius | Field corner radius |
| Part | Description |
wrapper | The bordered field container |
input | The native <input> element |
Props
| Attribute | Type | Default | Description |
label | string | "" | Label text |
value | string | "" | Current value |
name | string | "" | Name submitted with the form |
placeholder | string | "" | Placeholder text |
type | string | "text" | Native input type (text, email, password, number, search, tel, url) |
size | xs | sm | md | lg | "md" | Size variant |
disabled | boolean | false | Disables the input |
required | boolean | false | Marks as required |
readonly | boolean | false | Read-only — focusable but not editable |
loading | boolean | false | Shows a spinner; implies non-interactive |
clearable | boolean | false | Shows × button when the field has a value |
invalid | boolean | false | Marks as invalid and shows error message |
helper-text | string | "" | Help text shown below the input |
error-message | string | "" | Error message shown when invalid |
autocomplete | string | "off" | Native autocomplete hint |
maxlength | number | — | Maximum character length |
minlength | number | — | Minimum character length |
input-id | string | auto-generated | Forwarded to the native input as id |
Slots
| Slot | Description |
prefix | Icon or content rendered inside the input on the left |
suffix | Icon or content rendered inside the input on the right (hidden when loading is true) |
Events
| Event | Detail | Description |
nana-input | { value: string } | Every keystroke — mirrors the native input event |
nana-change | { value: string } | Value committed — fires on blur or Enter |
nana-clear | — | Clear button was clicked or .clear() was called |
nana-focus | — | Input gained focus |
nana-blur | — | Input lost focus |
Methods
| Method | Parameters | Description |
focus() | options?: FocusOptions | Focuses the native input |
blur() | — | Removes focus from the input |
clear() | — | Clears the value and fires nana-clear |