Input
A one-line text field with its label, a hint or an error under it, an optional clear button and room for icons.
- spredo-crypto-screenerCrypto screener
- playwright-msi-scraperScraper on Playwright
- The × shows while there is text; it empties the field and puts the caret back in it.
- Click anywhere in the box, on the icon too: the field takes focus.
- Fix the package name: the error under it turns back into the description.
Field marks
- Import
import { Input } from "cennge-ui";- Size
- 3.2 KB gzip for this import, its dependencies aside
- Pattern
- Native
<input>with a<label> - State
- Controlled with
valueandonValueChange, or uncontrolled withdefaultValue - SSR
- Rendered on the server in the Next.js check; hydrates without a warning
- Hint and error
- Linked with
aria-describedby; the error replaces the hint - Forms
- Errors from
<Form>show under the field byname - Tests
- Unit tests
- Source
- src/components/input
When to use#
Use an Input for one line of free text: a name, an email, a search. It renders a real <input>, so type, name, autoComplete, pattern and every other attribute reach it as they are, and onValueChange hands you the string without reading the event.
Similar#
- Textarea takes several lines and grows with them.
- NumberInput is for numbers: it refuses letters, steps with the arrows and formats by locale.
- PasswordInput adds a show/hide button and a Caps Lock hint.
- Combobox is a text field whose value must come from a list.
Validate when the field is left#
isInvalid and errorMessage are yours to set, so you choose when to check. Here the address is checked once focus has left the field, not while it is being typed.
import { useState } from "react";
import { Input } from "cennge-ui";
export default function ContactEmail() {
const [email, setEmail] = useState("");
const [isTouched, setTouched] = useState(false);
const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
return (
<Input
type="email"
label="Security contact"
placeholder="dev@example.com"
description="Reports about payload-cache go here"
value={email}
onValueChange={setEmail}
onBlur={() => setTouched(true)}
isInvalid={isTouched && !isValid}
errorMessage="Enter an address like dev@example.com"
className="w-full max-w-80"
/>
);
}Prefix and suffix#
startContent and endContent sit inside the box, before and after the text. A click on them still focuses the field.
import { Input } from "cennge-ui";
export default function PreviewAddress() {
return (
<div className="flex w-full max-w-md flex-col gap-4">
<Input
label="Repository"
defaultValue="cennge-ui"
startContent={<span className="text-sm text-muted select-none">github.com/Cennge/</span>}
/>
<Input
label="Preview address"
defaultValue="payload-cache"
startContent={<span className="text-sm text-muted select-none">https://</span>}
endContent={<span className="text-sm text-muted select-none">.example.com</span>}
/>
</div>
);
}Variants#
variant sets the box and color the focus colour, primary by default. size and radius take the same values as on Button.
import { Input, type InputVariant } from "cennge-ui";
const variants: InputVariant[] = ["flat", "bordered", "faded", "underlined"];
export default function InputVariants() {
return (
<div className="grid w-full max-w-xl gap-x-6 gap-y-5 sm:grid-cols-2">
{variants.map((variant) => (
<Input key={variant} variant={variant} label={variant[0]!.toUpperCase() + variant.slice(1)} placeholder="ai-niche-scout" />
))}
</div>
);
}Checked by the browser#
Inside a Form, the browser's own checks (isRequired, type="email", pattern) show their message under the field instead of in a tooltip, and a blocked submit focuses the first field to fix. Press Invite with the email empty.
import { Button, Form, Input, toast } from "cennge-ui";
export default function InviteForm() {
return (
<Form
className="flex w-full max-w-80 flex-col gap-4"
onSubmit={(event) => {
event.preventDefault();
const data = new FormData(event.currentTarget);
toast.success("Invitation sent", { description: `${data.get("email")} can now push to ${data.get("repository")}` });
}}
>
<Input name="email" type="email" label="Email" placeholder="dev@example.com" isRequired />
<Input
name="repository"
label="Repository"
defaultValue="cennge-ui"
isRequired
pattern="[a-z0-9\-]+"
description="Lowercase letters, digits and dashes"
/>
<Button type="submit" color="primary" className="self-start">
Invite
</Button>
</Form>
);
}Accessibility#
The label is a <label for> the input, and the description or the error is its aria-describedby, so a screen reader reads it with the field. isInvalid sets aria-invalid. isRequired sets the native required attribute; the asterisk after the label is drawn in CSS. The clear button is a real button named "Clear", reachable with Tab, and it hands focus back to the field.
Styling#
className goes to the outer wrapper, which holds the label, the box and the text under it. classNames reaches the parts inside: base, label, inputWrapper (the box), input, clearButton, description and errorMessage.
Props#
Also takes every attribute of <input>, except size, color, value, defaultValue, which mean something else here.