Components
Form Item
A self-managing form field that renders a finished control per variant — label, input and a description / error line in a fixed vertical layout with built-in validation
Installation
- $npx litefy@latest add form-item
- $pnpm dlx litefy@latest add form-item
- $yarn dlx litefy@latest add form-item
- $bun --bun litefy@latest add form-item
Usage
FormItem renders a finished control per variant with label, description and validation built in.
"use client";
import { useRef } from "react";
import { Button, Form, FormItem, type FormRef } from "@/ui";
export default function Demo() {
const formRef = useRef<FormRef>(null);
return (
<Form
ref={formRef}
className="w-sm space-y-4"
onSubmit={async (values) => {
console.log(values);
return true;
}}
>
<FormItem
variant="input"
name="username"
label="Username"
required
description="3-16 characters"
validate={(value) => {
if (!value) return "Username is required";
if (value.length < 3 || value.length > 16) return "Must be 3-16 characters";
return null;
}}
/>
<FormItem
variant="password"
name="password"
label="Password"
required
validate={(value) => (value.length >= 8 ? null : "At least 8 characters")}
/>
<FormItem
variant="select"
name="role"
label="Role"
validate={(value) => (value ? null : "Pick a role")}
controlProps={{
options: [
{ label: "Developer", value: "developer" },
{ label: "Designer", value: "designer" },
],
}}
/>
<FormItem
variant="number-input"
name="age"
label="Age"
validateTrigger="onChange"
validate={(value) => {
if (!value) return null;
const n = Number(value);
return Number.isFinite(n) && n >= 1 && n <= 120 ? null : "1-120";
}}
/>
<div className="flex gap-2 pt-2">
<Button type="submit">Submit</Button>
<Button type="button" variant="outline" onClick={() => formRef.current?.reset()}>
Reset
</Button>
</div>
</Form>
);
}
Disabled
Set disabled to freeze a field's control.
"use client";
import { Form, FormItem } from "@/ui";
export default function FormItemDisabledDemo() {
return (
<Form className="w-sm space-y-4" onSubmit={async () => true}>
<FormItem
variant="select"
name="plan"
label="Plan"
disabled
controlProps={{
defaultValue: "free",
options: [
{ label: "Free", value: "free" },
{ label: "Pro", value: "pro" },
],
}}
/>
</Form>
);
}
Validation
validate returns a string for a hard error, or { message, invalid: false } for a message-only hint, e.g. empty required fields.
"use client";
import { Form, FormItem } from "@/ui";
export default function Demo() {
return (
<Form className="w-md" onSubmit={async () => true}>
<FormItem
variant="input"
name="repo"
label="Repository"
description="Required. Letters, numbers and dashes once filled."
validate={(value) => {
if (value === "") return { message: "Repository is required", invalid: false };
if (!/^[a-z0-9-]+$/i.test(value)) return "Only letters, numbers and dashes";
return null;
}}
controlProps={{ placeholder: "litefy-ui" }}
/>
</Form>
);
}
API Reference
FormItem
A form field rendered as a finished control.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "input" | "textarea" | "select" | "password" | "number-input" | "input" | Which finished control to render |
name | string | - | Field name — also the key the surrounding Form uses to collect values. Required |
label | React.ReactNode | - | Label rendered above the control |
description | React.ReactNode | - | Helper text rendered below the control; replaced by the error message while the field is invalid |
required | boolean | - | Marks the label and makes the control required |
validate | (value: string) => string | { message: string; invalid?: boolean } | boolean | null | undefined | Promise<...> | - | A string is a hard error (message + aria-invalid on the control); { message, invalid: false } shows the message without styling the control; false is a bare invalid state; null/undefined/true pass |
validateTrigger | "onChange" | "onBlur" | "onBlur" | When validate runs. Once the field is invalid, it also re-validates on every change |
disabled | boolean | - | Disables the control |
className | ClassNameValue | - | Root container custom styles |
classNames | { label?; description?; error? } — each ClassNameValue | - | Custom classes for the label, description and error lines |
controlProps | Per variant, see below | {} | Props forwarded to the underlying control |
controlProps by variant:
| Variant | controlProps type |
|---|---|
input | Omit<InputProps, "ref"> |
textarea | Omit<TextareaProps, "ref"> |
select | Omit<SelectProps, "ref"> & { options: (SelectOption | SelectOptionGroup)[] } |
password | Omit<PasswordProps, "ref"> |
number-input | Omit<NumberInputProps, "ref"> |
Custom
onChange/onBlurincontrolPropsare wrapped, not replaced; theselectvariant carries its value in a hidden input for native form submission.