Form

Input

Inputs capture a single line of free-text or numeric data.

Components · Form

An input captures a single value typed by the user — text, a number, a date, a currency amount, a color. Namirial inputs are built from --nm-input-* tokens defined in namirial-theme.css, ship in five sizes, and share the same .nm-input__label and .nm-input__help parts used by select and search.

We'll never share your email.
Looks good.

Overview

An input is the default control for collecting a single line of free-form text or data, such as a name, email address, amount, or date. Always pair it with a clear label. When a specific format is expected, provide helpful guidance before users enter their information.

When to use

Use an input when:

  • You need to collect a single line of free-form data and the range of valid responses is too broad to present as a predefined list.
  • The input is part of a composite field, paired with a unit, symbol, or another control (for example, a currency selector or measurement unit).

Consider another component when:

  • Users need to choose from a fixed set of options (use Select, Radio buttons, or Checkboxes).
  • Users need to enter longer or multi-line content (use Textarea).
  • The value requires a specialised input pattern or interaction (e.g. Code, Search).

Required fields
Do not indicate required fields with an asterisk (*). Use the label “(required)” instead.

If all or most fields in a form are mandatory, indicate this at the beginning of the form by using one of the following statements:

  • “All fields are required.”
  • “All fields are required unless otherwise indicated.”

In forms where only some fields are optional, mark those fields with “(optional)”.

Anatomy

An input is a label, a bordered container, and an optional help text — three independently-styled parts, each with its own token group. Icons and prefixes live inside the container, flanking the field.

Tokens and values that drive each part

PartRoleSource
LabelText color--nm-input-label-fg
Required markColor--nm-input-label-required
Optional markColor--nm-input-label-optional
ContainerFill--nm-input-bg
ContainerFill on hover--nm-input-bg-hover
ContainerFill, read-only--nm-input-bg-readonly
ContainerBorder--nm-input-border
ContainerRadiusborder-radius — 6 / 10 / 12 / 12 px (xs → xl)
FieldText color--nm-input-text
FieldPlaceholder color--nm-input-placeholder
Leading / trailing iconColor--nm-input-text — neutral, unaffected by validation intent
Status iconColor--nm-input-icon — recolored by validation intent
Help textColor--nm-input-help-fg
Focus ringColor--nm-input-focus-ring, box-shadow 2px, no offset

Sizes

Input ships in five sizes, matching button's xs–xl scale. Match the size to the density of the surrounding form.

xs
12px · h 24px · r 6
sm
14px · h 32px · r 8
md
16px · h 40px · r 10
lg
20px · h 48px · r 12
xl
24px · h 56px · r 12

Choosing a size

  • xs — dense tables, inline editing, filter bars
  • sm — default for forms and dialogs
  • md — standard content forms, cards
  • lg — prominent single-field forms, onboarding
  • xl — signing surfaces, marketing lead-capture

Content types

The field between the label and the help text adapts to the kind of value it captures. All variants share the same .nm-input__container; only the contents change.

Default

Icon left / Icon right

Use .nm-input__icon-leading for an icon that identifies the field's format (an envelope for email). Use .nm-input__icon-trailing for a secondary content icon, like a chevron for select. Both always render in the same neutral color as the field's own text — they identify or decorate, they don't report validity. For that, use the dedicated .nm-input__icon-status (see Interaction & validation states) — it's the only icon element that changes color with nm-input--error / nm-input--warning / nm-input--success, and it can appear alongside a leading or trailing content icon rather than replacing it.

Clear

A trailing icon becomes an actual clear action once the field has a value — render it as a <button>, not a decorative icon.

Label

.nm-input__label accepts an optional required or optional marker. Mark a field required only when the user must actively provide it — most fields don't need either marker.

Bare · required · optional

Pattern examples

Common recipes built by composing the same .nm-input parts differently — nothing here is a new component.

Currency / Percentage

Currency and percentage both render their affix as an icon rather than literal text — .nm-input__icon-leading for the prefix, .nm-input__icon-trailing for the suffix — so they pick up the same per-size icon dimensions as every other icon in the system, with no divider or boxed compartment involved.

Color picker

.nm-input__color-swatch renders the current value; pair it with a visually-hidden native <input type="color"> (.nm-input__color-native) so the platform color picker still opens on click.

Date

Native <input type="date"> is restyled to hide the default calendar icon so a leading calendar icon from the system's icon set can take its place.

Enter a valid date.

A content icon (like calendar) always stays neutral — it identifies the field's format, not its validity. A validation icon uses the separate .nm-input__icon-status slot, which is the only icon element recolored by nm-input--error / nm-input--warning / nm-input--success. Both slots can be present in the same container at once.

Interaction & validation states

Every input responds to hover and focus, can be locked read-only, and carries three validation intents that recolor the border, help text, and required mark together.

Default → hover → focus → read-only

Enter a valid email address.
This password is weak.
Username is available.

Error · warning · success — nm-input--error, nm-input--warning, nm-input--success

No disabled state

Like every Namirial control, inputs never ship a browser-default disabled look. Use read-only (nm-input--readonly) when a value can't change right now but should still be visible and still submit with the form.

Focus

Focus is rendered as a 2px box-shadow ring in --nm-input-focus-ring, not an outline — it sits flush against the container instead of floating outside it. It triggers on :focus-within, so clicking anywhere in the container (including a leading icon) focuses the field.

Light & dark theme

Input tokens are remapped under .dark. Contrast and hover directionality are handled for you.

Light data-theme="light"
We'll never share your email.
Dark data-theme="dark"
We'll never share your email.

Both previews are locked — they don't follow the page theme switcher.

Do & don't

✓Do
Format: ORD-000000
Show the expected format as help text, not as a placeholder that disappears once typing starts.
✕Don't
Don't rely on placeholder text alone to convey the required format.
✓Do
Enter a valid email address.
Pair an error border with a help text that says exactly what to fix.
✕Don't
Don't show a red border with no explanation and icon — this is not accessibile at all and it can not be recognized what is wrong.
✓Do
Use read-only for a value the user can see and submit but can't edit here.
✕Don't
Don't grey out a field with the native disabled state — its value drops out of the submitted form.
✓Do
Size the field to roughly match the expected content length.
✕Don't
Don't make every field full width regardless of what it holds — it removes a useful visual cue.

Accessibility

Semantics

  • Always associate the label with the field — wrap both in .nm-input and use for/id, or nest the <input> inside the <label>.
  • Use the correct type (email, tel, number, date) so mobile keyboards and browser validation match the data.

Help & error text

  • Connect help and error text to the field with aria-describedby so screen readers announce it alongside the label.
  • On error, add aria-invalid="true" to the <input> in addition to the nm-input--error class — the class is visual only.

Keyboard

  • Tab moves focus into the field; all native text-editing shortcuts work unmodified.
  • Focus uses :focus-within on the container, so clicking a leading/trailing icon also focuses the field.

Labeling

  • Icon-only trailing actions (like Clear) must be a real <button> with an aria-label, never a bare icon with a click handler.
  • Never rely on the border color alone to communicate validation state — pair it with visible help text.

Playground

We'll never share your email.
<div class="nm-input nm-input--md"> <label class="nm-input__label">Email address <span class="nm-input__required">(required)</span></label> <div class="nm-input__container"> <span class="nm-input__icon-leading"></span> <input class="nm-input__field"> </div> <span class="nm-input__help">We'll never share your email.</span> </div>

Token reference

TokenRole
--nm-input-bgContainer fill, default
--nm-input-bg-hoverContainer fill, hover
--nm-input-bg-readonlyContainer fill, read-only
--nm-input-borderContainer border — recolored by --error / --warning / --success
--nm-input-focus-ringFocus box-shadow color
--nm-input-textField value color
--nm-input-placeholderPlaceholder color
--nm-input-iconStatus icon color — recolored by validation intent
--nm-input-label-fgLabel text color
--nm-input-label-requiredRequired mark color — recolored by validation intent
--nm-input-label-optionalOptional mark color
--nm-input-help-fgHelp text color — recolored by validation intent

Implementation

No JavaScript is required for a plain input — it's native HTML styled by CSS classes.

<link rel="stylesheet" href="namirial-theme.css">

<div class="nm-input nm-input--md">
  <label class="nm-input__label">
    Email address
    <span class="nm-input__required">(required)</span>
  </label>
  <div class="nm-input__container">
    <input class="nm-input__field" placeholder="you@company.com">
  </div>
  <span class="nm-input__help">We'll never share your email.</span>
</div>

CSS classes

ClassValuesDefault
nm-input--{size}xs · sm · md · lg · xlsm
nm-input--{intent}error · warning · success—
nm-input--readonlyLocks the container against edits—
nm-input__container--comboSplits the container into a fixed compartment and the field, separated by nm-input__combo-divider—
nm-input__icon-leading / nm-input__icon-trailingPositions a neutral content icon inside the container—
nm-input__icon-statusPositions the validation icon — the only one recolored by nm-input--error / nm-input--warning / nm-input--success; can coexist with a leading/trailing icon—
nm-input__required / nm-input__optionalMarks the label—