Forms & Inputs
Input
A text input with label, helper text, validation, prefix/suffix and clear support.
Preview
Section titled “Preview”<nana-input
label="Email"
placeholder="you@example.com"
style="min-width: 16rem"
></nana-input> Installation
Section titled “Installation”The input ships for all three targets. Pick the tab for your project — the CLI detects which you are on and serves the matching implementation.
npx @nana-tec/ui add inputLands as src/components/ui/input/input.tsx, styled with Tailwind classes that
resolve to the design tokens. Requires Tailwind v4.
npx @nana-tec/ui add inputLands in src/lib/components/ui/input/ as Input.svelte, styled with Tailwind
classes that resolve to the design tokens. Requires Tailwind v4.
Install the package for a versioned dependency:
npm install @nana-tec/ui-componentsOr copy the source in and own it:
npx @nana-tec/ui add inputimport { Input } from "@/components/ui/input";
export function Example() { const [email, setEmail] = useState("");
return ( <Input label="Email" type="email" required value={email} onChange={(event) => setEmail(event.target.value)} helperText="We never share it." /> );}errorMessage replaces helperText and wires up aria-invalid and
aria-describedby. prefix and suffix take nodes for icons or units. The
label is associated automatically — pass your own id to override the
generated one.
<script lang="ts"> import { Input } from '$lib/components/ui/input';
let email = $state('');</script>
<Input label="Email" type="email" required bind:value={email} helperText="We never share it."/>errorMessage replaces helperText and wires up aria-invalid and
aria-describedby. prefix and suffix are snippets for icons or units, and
the label is associated automatically.
import "@nana-tec/ui-components/input";<nana-input label="Email" placeholder="you@example.com" style="min-width:16rem"></nana-input>Classes you pass to the React and Svelte components are merged with
tailwind-merge, so an override wins rather than fighting. See
Using Tailwind.
<nana-input
label="Email"
type="email"
placeholder="you@example.com"
style="max-width: 24rem"
></nana-input>
<nana-input
label="Password"
type="password"
placeholder="Enter password"
style="max-width: 24rem"
></nana-input>
<nana-input
label="Number"
type="number"
placeholder="42"
style="max-width: 24rem"
></nana-input>
<nana-input
label="Search"
type="search"
placeholder="Search…"
style="max-width: 24rem"
></nana-input> <nana-input
label="Extra small"
size="xs"
placeholder="xs"
style="max-width: 24rem"
></nana-input>
<nana-input
label="Small"
size="sm"
placeholder="sm"
style="max-width: 24rem"
></nana-input>
<nana-input
label="Medium"
size="md"
placeholder="md"
style="max-width: 24rem"
></nana-input>
<nana-input
label="Large"
size="lg"
placeholder="lg"
style="max-width: 24rem"
></nana-input> States
Section titled “States”<nana-input label="Disabled" placeholder="Disabled" disabled></nana-input>
<nana-input label="Readonly" value="Read-only value" readonly></nana-input>
<nana-input label="Loading" placeholder="Checking…" loading></nana-input>
<nana-input
label="Invalid"
value="bad-value"
invalid
error-message="Please enter a valid email address"
></nana-input> Prefix & suffix
Section titled “Prefix & suffix”Slot an icon (or any content) into the prefix / suffix slots.
<nana-input label="Search" placeholder="Search…" style="max-width: 24rem">
<nana-icon slot="prefix" name="search"></nana-icon>
</nana-input>
<nana-input label="Website" placeholder="example.com" style="max-width: 24rem">
<nana-icon slot="suffix" name="open_in_new"></nana-icon>
</nana-input> The React and Svelte props, read from the components’ own types:
Input
| Prop | Type | Default |
|---|---|---|
size | InputSize | 'md' |
label | ReactNode | |
helperText | ReactNode | |
errorMessage | ReactNode | |
prefix | ReactNode | |
suffix | ReactNode |
Also accepts Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'prefix'>.
Input
| Prop | Type | Default |
|---|---|---|
size | InputSize | 'md' |
valuebind: | string | '' |
label | string | |
helperText | string | |
errorMessage | string | |
prefix | Snippet | |
suffix | Snippet |
Also accepts Omit<HTMLInputAttributes, 'class' | 'size' | 'value'>.
The attribute names, events and CSS hooks below are the Web Components reference.
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
autocomplete | autocomplete | HTMLInputElement['autocomplete'] | 'off' | |
clearable | clearable | boolean | false | |
disabled | disabled | boolean | false | |
errorMessage | error-message | string | '' | |
helperText | helper-text | string | '' | |
inputId | inputId | — | — | |
inputMode | inputmode | string | '' | Which virtual keyboard to raise on touch devices. Distinct from type: type="text" inputmode="decimal" keeps free-form input while still opening a numeric pad, which is what amount and phone fields want. inputMode is also a global HTMLElement property, so this deliberately overrides it: on the host it is inert (the host is not editable), and the value only does anything once forwarded to the inner <input> below. |
invalid | invalid | boolean | false | |
label | label | string | '' | |
loading | loading | boolean | false | |
max | max | string | undefined | — | Upper bound for type="number" / type="date" and friends. |
maxlength | maxlength | number | undefined | — | |
min | min | string | undefined | — | Lower bound for type="number" / type="date" and friends. |
minlength | minlength | number | undefined | — | |
name | name | string | '' | |
pattern | pattern | string | undefined | — | Regular expression the value must match, per native constraint validation. |
placeholder | placeholder | string | '' | |
readonly | readonly | boolean | false | |
required | required | boolean | false | |
size | size | 'xs' | 'sm' | 'md' | 'lg' | 'md' | |
step | step | string | undefined | — | Granularity of the accepted value; any disables stepping. |
type | type | HTMLInputElement['type'] | 'text' | |
value | value | string | '' |
Events
| Event | Description |
|---|---|
nana-input | Fired on every keystroke (detail.value) |
nana-change | Fired when the value is committed (detail.value) |
nana-clear | Fired when the clear button or Escape empties the field |
nana-focus | Fired when the field gains focus |
nana-blur | Fired when the field loses focus |
Slots
| Slot | Description |
|---|---|
prefix | Content before the field, in a fixed-width zone |
suffix | Content after the field, in a fixed-width zone |
CSS parts
| Part | Description |
|---|---|
::part(wrapper) | The bordered field container |
::part(input) | The native <input> element |
CSS custom properties
| Property | Description |
|---|---|
--nana-input-bg | Field background colour |
--nana-input-color | Input text colour |
--nana-input-radius | Field corner radius |
--nana-input-height | Field height (defaults to the size preset) |
--nana-input-affix-inset | Gap from the border to a slotted prefix/suffix |
--nana-input-affix-gap | Gap between a slotted prefix/suffix and the text |
--nana-input-affix-zone | Reserved width for a slotted prefix/suffix |
--nana-input-affix-icon-size | Size applied to a slotted icon, svg or img |
CSS Variables
Section titled “CSS Variables”| Variable | Description |
|---|---|
--nana-input-bg | Field background colour |
--nana-input-color | Input text colour |
--nana-input-radius | Field corner radius |
CSS Parts
Section titled “CSS Parts”| Part | Description |
|---|---|
wrapper | The bordered field container |
input | The native <input> element |
Accessibility
Section titled “Accessibility”- Keyboard accessible
- Screen-reader labels
- Focus-visible states
- ARIA roles and states
- WCAG 2.2 AA contrast