Field
The frame every field shares: label, icon, hint, error. Wrap your own control in it.
Preview
import { Input, Stack } from '@kerf-js/veneer'export function Anatomy() { return ( <Stack gap="group"> <Input label="Domain" icon="globe" prefix="https://" suffix=".kerf.dev" hint="You can add your own later." defaultValue="storefront" clearable /> <Input label="Release name" hint="Shown in the deploy list." counter={40} defaultValue="Autumn sale" /> </Stack> )}Installation
npm install @kerf-js/veneerTry it
Change the controls: the preview and its code update together. The address keeps what you set, so you can link to it.
import { Field } from '@kerf-js/veneer'<Input label="Domain" hint="You can add your own later." prefix="https://" suffix=".kerf.dev" />Usage
import { Field } from '@kerf-js/veneer'Variants
Use variant to put the label on the border, which is the default, or classic to put it above the field.
import { Grid, Input } from '@kerf-js/veneer'export function LabelStyles() { return ( <Grid cols={2} gap="group" > <Input label="Project name" hint="Floats on the border." /> <Input label="Project name" hint="Sits above the field." variant="classic" /> </Grid> )}Validation
An error takes the hint's place, so the layout doesn't jump.
import { Input, Stack } from '@kerf-js/veneer'export function Error() { return ( <Stack gap="group"> <Input label="Project name" hint="Lowercase letters and dashes." defaultValue="storefront" /> <Input label="Project name" hint="Lowercase letters and dashes." defaultValue="Store Front" error="That name has a space in it." /> </Stack> )}An action on the edge
Input takes every Field prop, so end adds to the right edge.
import { state } from '@kerf-js/core'import { Button, Input } from '@kerf-js/veneer'export function CustomControl() { const token = state('tsl_4f9c2e71') const rotate = () => { const random = Math.random().toString(16) token.value = `tsl_${random.slice(2, 10)}` } return ( <Input label="Deploy token" hint="Rotating it stops the old one straight away." end={ <Button size="sm" emphasis="ghost" tone="neutral" onClick={rotate} > Rotate </Button> } value={token.value} readonly /> )}With form
Fields stack into a form, and autocomplete lets the browser fill in the name and email.
import { Input, Stack, Textarea } from '@kerf-js/veneer'export function Form() { return ( <Stack gap="group"> <Input label="Name" autocomplete="name" defaultValue="Mara Lindqvist" /> <Input label="Work email" type="email" autocomplete="email" defaultValue="mara@kerf.dev" /> <Textarea label="About you" hint="Shown to your team." /> </Stack> )}When to use
Accessibility
- Use the
idanddescribedbythatcontrolreceives, so the label, hint and error stay tied to your control.
Theming
Restyle it app-wide from veneer() in your kerf config. Every key takes colour, spacing, size, radius, shadow, type and motion; its own keys are listed. A state nests inside: hover: { bg: 'ink' }. Text on a fill you set is contrast-checked at build. How theming works
| Key | Own keys | States |
|---|---|---|
parts.field | bg, radius | hover, focus, press, active, open, disabled |
parts.field.label | text | hover, focus |
veneer({ colors: { brand: '#e8590c', ink: '#1b1b1f' }, parts: { field: { radius: 4, label: { focus: { text: 'brand' } } }, },})API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | The name people read and screen readers announce. Always required, and it floats on the border by default. |
hint | unknown | — | A short line of help under the field. |
error | unknown | — | Replaces the hint and marks the field invalid. |
icon | IconRef | — | An icon before the control. |
prefix | unknown | — | Fixed text before the value. |
suffix | unknown | — | Fixed text after the value. |
variant | "floating" | "classic" | — | floating puts the label on the border, classic above the field. |
appearance | "line" | — | line draws only the bottom edge. |
size | "sm" | "md" | "lg" | — | sm, md or lg. Each follows the density around it. |
clearable | boolean | false | A clear button, shown once there is a value. |
counter | number | — | The most it should hold. Draws a count under the field. |
id | string | — | The element id. One is made for you if you leave it out. |
value | string | — | The current value, when you own the state. |
disabled | boolean | false | Can't be focused or changed, and doesn't post with a form. |
readonly | boolean | false | Can be focused, read and copied, but not changed. It still posts. |
control | Control | — | Draws the control. Gets id, describedby, invalid and the remaining props. |
end | unknown | — | Sits at the right edge of the frame, after any suffix. |
filled | boolean | false | Keeps the label up when your control holds a value the field can't see. |
| Event | Type | Default | Description |
|---|---|---|---|
onInput | ((e: InputEvent & { currentTarget: HTMLInputElement & HTMLTextAreaElemen… | — | Called on each keystroke. |