Input
Plain CSS on an ordinary <input>, so the browser’s own validation, autofill and keyboard handling work as before.
How to use it
Section titled “How to use it”<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.0/src/tokens/tokens.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.0/src/components/css/input/input.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.0/src/components/css/label/label.css"><!-- Or everything in one file: https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.0/dist/lotek.css --><label class="lt-label" for="email">Email address</label><input class="lt-input" id="email" type="email" placeholder="alex@example.com" />
<label class="lt-label" for="confirm">Confirm email address</label><input class="lt-input" id="confirm" type="email" data-state="success" value="alex@example.com" />
<label class="lt-label" for="backup">Backup email</label><input class="lt-input" id="backup" type="email" data-state="invalid" aria-invalid="true" value="alex@" />
<label class="lt-label" for="registered">Registered email address</label><input class="lt-input" id="registered" type="email" value="alex@example.com" disabled />import "@lo-tek/interfaces/tokens.css"import "@lo-tek/interfaces/input.css"import "@lo-tek/interfaces/label.css"lt is used in two ways, and which one depends on whether you create the element or find it.
If you create the markup, you spread the attributes in. That is the route in React, Astro and anything else that renders:
import { lt } from "@lo-tek/interfaces/react"
<input {...lt.input({ type: "email", state: "invalid" })} id="email" />The import goes to /react here, since the block is JSX. From there the keys are called className and htmlFor. In Astro and anything else without React you import @lo-tek/interfaces and get class and for.
If the element is already there, you set them on it. That is the route when you enhance HTML a server or a template has sent:
import { lt, type FieldState } from "@lo-tek/interfaces"
const field = document.querySelector<HTMLInputElement>("#email")if (!field) throw new Error("field not found")
lt.setAttributes(field, lt.input({ type: "email" }))type sets both the HTML type and the icon variant, so they cannot get out of step. With lt.input() that trap cannot be walked into: a value that does not exist is a type error.
On validation you call it again with the new state:
field.addEventListener("blur", () => { const state: FieldState = field.validity.valid ? "default" : "invalid"
lt.setAttributes(field, lt.input({ type: "email", state }))})state: "invalid" sets both data-state for the colour and aria-invalid for the screen reader. When the field goes back to "default", lt.setAttributes removes both. Without that, the field would stay red after the error was corrected.
Values from outside: lt.input.isType(x) and lt.input.isState(x). See Type-safe use.
data-state | Used for |
|---|---|
| (omitted) | Normal state |
invalid | Validation has failed |
success | The value is confirmed valid |
data-state is colour. Screen readers also need aria-invalid="true", or Field, which sets both.
Date and time
Section titled “Date and time”<label class="lt-label" for="date">Travel date</label><input class="lt-input" id="date" data-variant="date" type="date" value="2026-05-31" />
<label class="lt-label" for="time">Departure time</label><input class="lt-input" id="time" data-variant="time" type="time" value="14:30" />Only date, time and datetime-local affect the appearance. The other types exist so that the TypeScript helper can set type for you.
Which date component?
Section titled “Which date component?”| Situation | Component |
|---|---|
| The browser’s own date picker is enough | Input with type="date" |
| You need to ask for a date | Dates in a form |
Classes you can style
Section titled “Classes you can style”| Class | What it is |
|---|---|
.lt-input | The field. The state comes from data-state, the icon from data-variant. |
| Variable | Default |
|---|---|
--lt-input-width | 100% |
--lt-input-padding | var(--lt-spacing-2) var(--lt-spacing-3) |
--lt-input-radius | var(--lt-spacing-1) |
An example. A form to be filled in on a phone can take larger touch targets, and your organisation may want square corners:
<label class="lt-label" for="default">Customer number</label><input class="lt-input" id="default" type="text" />
<div class="roomy-form"><label class="lt-label" for="roomy">Customer number</label><input class="lt-input" id="roomy" type="text" /></div>.roomy-form {--lt-input-padding: var(--lt-spacing-3) var(--lt-spacing-4);--lt-input-radius: 0;}The variable is on the container, not on the field, so it applies to every field in the form without you touching any of them.
See Customising for how the variables and the layer work.
Accessibility
Section titled “Accessibility”- Every field needs a visible
<label>withfor.placeholderis no replacement, since it disappears as soon as the user types. - Use the right
type. It decides the phone keyboard and what autofill the browser offers. - Set
autocompleteon fields that ask for personal details, for exampleautocomplete="email".