Skip to content
Nizivo UI

Input

A single-line native text input in a bordered box, with optional icons and text affixes. Inside a Field it is labelled, described and validated by the Field.

Tier: client

Import

import { Input } from '@nizivo/react/input';

// Once per app (Getting started), after
// @nizivo/tokens/tokens.css and @nizivo/react/css/base.css
import '@nizivo/react/css/input.css';

Static or interactive

Client. It always ships its client leaf, which reads its Field through FieldScope, so a page with an Input also loads FieldScope. The server HTML is a native <input> that works before hydration. How to choose.

Text size on touch screens

Under a coarse pointer (a phone or a tablet), the text, the placeholder and the affixes are 16 px (Body/L) instead of 14 px (Body/M) at every size, because iOS Safari zooms the page into a focused field whose text is smaller than 16 px. The box keeps its 32, 40 or 48 px height. --nz-input-font replaces the font on every pointer, so a page that sets it also decides whether iOS zooms.

Examples

Basic

Inside a Field: the label, help text and native validation messages are wired for you.

Shown on product pages and checkout.
import type { JSX } from 'react';
import { Field } from '@nizivo/react/field';
import { Input } from '@nizivo/react/input';

export function InputExample(): JSX.Element {
  return (
    <Field
      label="Product title"
      description="Shown on product pages and checkout."
      required
      messages={{ valueMissing: 'Enter a product title.' }}
    >
      <Input name="title" placeholder="e.g. Stoneware mug" />
    </Field>
  );
}

Sizes

32, 40 and 48 px, the heights of Button and Select.

import type { JSX } from 'react';
import { Input } from '@nizivo/react/input';

export function InputSizesExample(): JSX.Element {
  return (
    <div style={{ display: 'grid', gap: 12, maxInlineSize: 320 }}>
      <Input size="sm" aria-label="Small" placeholder="Small" />
      <Input aria-label="Medium" placeholder="Medium" />
      <Input size="lg" aria-label="Large" placeholder="Large" />
    </div>
  );
}

Slots

Decorative icons, and text affixes that join the input's description. A press anywhere in the box focuses the input.

₹per kg
import type { JSX } from 'react';
import { Field } from '@nizivo/react/field';
import { Input } from '@nizivo/react/input';

/** Stands in for an `@nizivo/icons` glyph (a magnifier). */
function SearchMark(): JSX.Element {
  return (
    <svg
      viewBox="0 0 24 24"
      width="1em"
      height="1em"
      fill="none"
      stroke="currentColor"
      strokeWidth={1.92}
      strokeLinecap="round"
    >
      <circle cx="11" cy="11" r="7" />
      <path d="m20 20-4-4" />
    </svg>
  );
}

export function InputSlotsExample(): JSX.Element {
  return (
    <div style={{ display: 'grid', gap: 12, maxInlineSize: 320 }}>
      <Input type="search" aria-label="Search products" leadingIcon={<SearchMark />} />
      <Field label="Price">
        <Input type="number" inputMode="decimal" prefix="₹" suffix="per kg" defaultValue="240" />
      </Field>
    </div>
  );
}

States

An error from the server, read-only and disabled.

Enter an email address like name@example.com.
import type { JSX } from 'react';
import { Field } from '@nizivo/react/field';
import { Input } from '@nizivo/react/input';

export function InputStatesExample(): JSX.Element {
  return (
    <div style={{ display: 'grid', gap: 16, maxInlineSize: 320 }}>
      <Field label="Email" error="Enter an email address like name@example.com.">
        <Input type="email" defaultValue="anna@" />
      </Field>
      <Field label="SKU" readOnly>
        <Input defaultValue="MUG-0042" />
      </Field>
      <Field label="Barcode" disabled>
        <Input defaultValue="8901234567890" />
      </Field>
    </div>
  );
}

Props

Client component Renders data-slot="input" with the class nz-input. Props: InputProps. Source: src/input/input.client.tsx.

NameTypeDefaultDescription
invalidboolean–

An error message is presented for this input (§8.3). Inside a Field, the Field sets it; an explicit value here wins.

leadingIconReactNode–

Decorative icon before the value (aria-hidden).

prefixReactNode–

Text before the value, such as a currency sign; joined to the input's aria-describedby.

renderRenderProp<InputState>–

Composition of the root, the bordered box around the native <input>. className and style go on the root too; every other prop, ref included, goes on the <input>.

sizeControlSize
"sm""md""lg"
'md'

32 / 40 / 48 px, as Button and Select.

suffixReactNode–

Text after the value, such as a unit; joined to the input's aria-describedby.

trailingIconReactNode–

Decorative icon after the value (aria-hidden).

typeInputType
"text""email""search""password""number""tel""url"
'text'
  • Also takes every attribute of <input> except children, prefix, size, type. (Omit<ComponentPropsWithRef<"input">, "type" | "size" | "prefix" | "children">)

render takes an element, or, in client code, a function of the props and this state (InputState):

NameTypeDefaultDescription
disabledrequiredboolean–
invalidrequiredboolean–

An error message is presented (the Field's, or the invalid prop).

readOnlyrequiredboolean–
sizerequiredControlSize
"sm""md""lg"
–

Other exports

InputProps type

import type { InputProps } from '@nizivo/react/input'

The props of Input, listed above.

InputState type

import type { InputState } from '@nizivo/react/input'

The render state of Input, listed above.

InputType type

import type { InputType } from '@nizivo/react/input'

Text-like input types; the others (checkbox, file, date, …) are other components.

type InputType = "text" | "email" | "search" | "password" | "number" | "tel" | "url"

Data attributes and slots

Style parts by class (.nz-<slot>) and state by data attribute. Attribute meanings come from contracts/vocabulary.json.

SlotRendered byData attributes
input
.nz-input
Input
root (takes render); interactive, focus: within
  • data-disabled (present or absent) ← disabled
    Mirrors the disabled attribute
  • data-invalid (present or absent) ← invalid
    An error message is currently presented (not "fails constraints"); always the Field's own state, never the engine's
  • data-readonly (present or absent)
    Mirrors the readonly attribute
  • data-size: "sm""md""lg" ← size
    The size prop; values in each component manifest
input-control
.nz-input-control
Input
inner element (CSS only)
none
input-icon
.nz-input-icon
Input
inner element (CSS only)
none
input-prefix
.nz-input-prefix
Input
inner element (CSS only)
none
input-suffix
.nz-input-suffix
Input
inner element (CSS only)
none

CSS hooks

Set a hook on the component's class or any ancestor to restyle it without specificity games; leave it unset for the default shown.

HookDefaultRead as
--nz-input-affix-fg--nz-text-secondary
  • color on .nz-input-prefix
  • color on .nz-input-suffix
--nz-input-bg
  • default: var(--nz-bg-surface)
  • data-readonly: var(--nz-bg-subtle)
  • background on .nz-input
--nz-input-bg-disabled--nz-bg-disabled
  • background on .nz-input:is([data-disabled], :has( > .nz-input-control:disabled))
--nz-input-border
  • default: var(--nz-border-control)
  • default: var(--nz-border-subtle)
  • data-invalid: var(--nz-border-critical)
  • border on .nz-input
--nz-input-border-hover
  • default: var(--nz-border-control-hover)
  • data-invalid: var(--nz-border-critical)
  • border-color on .nz-input:hover:not([data-disabled], :has( > .nz-input-control:disabled)) @media (hover: hover)
--nz-input-fg--nz-text-primary
  • color on .nz-input
--nz-input-fg-disabled--nz-text-disabled
  • color on .nz-input:is([data-disabled], :has( > .nz-input-control:disabled))
--nz-input-font
  • default: var(--nz-font-body-m)
  • default @media (pointer: coarse): var(--nz-font-body-l)
  • font on .nz-input
--nz-input-gap
  • default: var(--nz-space-2)
  • data-size="sm": var(--nz-space-1-5)
  • gap on .nz-input
--nz-input-height
  • default: var(--nz-size-control-md)
  • data-size="lg": var(--nz-size-control-lg)
  • data-size="sm": var(--nz-size-control-sm)
  • block-size on .nz-input
--nz-input-icon-fg--nz-icon-secondary
  • color on .nz-input-icon
--nz-input-padding-inline--nz-space-3
  • padding-inline on .nz-input
--nz-input-placeholder-fg--nz-text-tertiary
  • color on .nz-input-control::placeholder
--nz-input-radius
  • default: var(--nz-radius-md)
  • data-size="sm": var(--nz-radius-sm)
  • border-radius on .nz-input

Layers: nz.components. Classes: .nz-input, .nz-input-control, .nz-input-icon, .nz-input-prefix, .nz-input-suffix.

Tokens this stylesheet reads (29)

--nz-bg-disabled, --nz-bg-subtle, --nz-bg-surface, --nz-border-control, --nz-border-control-hover, --nz-border-critical, --nz-border-subtle, --nz-border-width-default, --nz-border-width-focus, --nz-duration-fast, --nz-easing-standard, --nz-focus-halo, --nz-focus-halo-critical, --nz-font-body-l, --nz-font-body-m, --nz-icon-secondary, --nz-radius-md, --nz-radius-sm, --nz-size-control-lg, --nz-size-control-md, --nz-size-control-sm, --nz-size-icon-sm, --nz-space-1-5, --nz-space-2, --nz-space-3, --nz-text-disabled, --nz-text-primary, --nz-text-secondary, --nz-text-tertiary

Accessibility

These parts are interactive. Each gets the focus outline and touch-action rule from @nizivo/react/css/base.css (the nz.base layer), so the floor survives a restyle:

  • input: draws the outline when the field inside it has focus.

Keyboard

KeysAction
TabMoves focus to the input. The whole box draws the focus outline; a press anywhere on the box, its icons or its affixes, focuses the input.
EnterSubmits the form the input belongs to (the browser's implicit submission). Inside a Field, a failed constraint shows its message under the input and focuses the first invalid control.

Source: a native <input> (input.client.tsx); the box's mousedown handler forwards focus to it. The browser lane (test/browser/text-controls.spec.tsx) checks the focus outline, the hit area and inline validation on submit.

Figma

Node ids from design/component-map.json, which nz gen writes from component.json. The links open the Nizivo Figma library, which may ask you to sign in.

On this page