Skip to content

Type to search the docs, API, and blog.

↑↓ navigate↵ openesc close
Zazz 0.7 alpha
API navigation

API

Input

Text inputs of every type, with optional icons.

Installation

pnpm dlx zazz-ui add input
npx zazz-ui add input
yarn dlx zazz-ui add input
bunx zazz-ui add input

Also adds fields, which it depends on. Run zazz-ui init first if the project has no zazz/ folder yet.

Examples

Default

We'll never sell or share your email address. Please enter a valid email address.
<div data-ui="field">
  <label data-field-slot="label" for="f-name">Name</label>
  <input
    data-ui="input"
    id="f-name"
    name="name"
    type="text"
    inputmode="text"
    placeholder="Ada Lovelace"
    autocomplete="name"
    autocapitalize="words"
    enterkeyhint="next"
  />
</div>
<div data-ui="field">
  <label data-field-slot="label" for="f-email">Email</label>
  <input
    data-ui="input"
    id="f-email"
    name="email"
    type="email"
    inputmode="email"
    required
    placeholder="you@example.com"
    autocomplete="email"
    autocapitalize="off"
    spellcheck="false"
    enterkeyhint="next"
    aria-describedby="f-email-hint"
  />
  <div data-field-slot="description">
    <span data-field-slot="hint" id="f-email-hint"
      >We'll never sell or share your email address.</span
    >
    <span data-field-slot="error" role="alert">Please enter a valid email address.</span>
  </div>
</div>
<div data-ui="field">
  <label data-field-slot="label" for="f-phone">Phone</label>
  <input
    data-ui="input"
    id="f-phone"
    name="phone"
    type="tel"
    inputmode="tel"
    placeholder="+1 (555) 012-3456"
    autocomplete="tel"
    enterkeyhint="next"
  />
</div>
<div data-ui="field">
  <label data-field-slot="label" for="f-date">Datetime</label>
  <input
    data-ui="input"
    id="f-date"
    name="date"
    type="datetime-local"
    inputmode="none"
    autocomplete="off"
  />
</div>
/**
 * input.css — Text input ([data-ui~="input"])
 *
 * @layer      variables, components
 * @requires   _layers.css, _variables.css, _properties.css (--_*-resolved, --_focus-shadow), fields.css, _reset.css
 * @uses       ::-webkit-calendar-picker-indicator — date/time picker chrome (WebKit)
 * @uses       mask — calendar icon (not background-image, for color: currentColor)
 * @tokens     --ui-input-* (@layer variables; surface colors and height read the
 *             --ui-field-* family directly)
 */

@layer variables {
  :root {
    --ui-input-w: 100%;
    --ui-input-min-w: 16ch;
    --ui-input-pl: var(--ui-field-px);
    --ui-input-pr: var(--ui-field-px);
    --ui-input-font-size: var(--ui-field-font-size);
    --ui-input-leading: var(--ui-field-leading);
    --ui-input-border-width: var(--ui-field-border-width);
    --ui-input-border-style: var(--ui-field-border-style);
    --ui-input-border-color: var(--ui-field-border-color);
    --ui-input-rounded: var(--ui-field-rounded);

    --ui-input-calendar-picker-color: currentColor;
    --ui-input-calendar-picker-icon-size: calc(var(--spacing) * 4);
    --ui-input-calendar-picker-size: calc(var(--spacing) * 6);
    --ui-input-calendar-picker-radius: var(--ui-field-inset-child-radius);
    --ui-input-calendar-picker-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Crect width='256' height='256' fill='none'/%3E%3Crect x='40' y='40' width='176' height='176' rx='8' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='16'/%3E%3Cline x1='176' y1='24' x2='176' y2='56' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='16'/%3E%3Cline x1='80' y1='24' x2='80' y2='56' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='16'/%3E%3Cline x1='40' y1='88' x2='216' y2='88' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='16'/%3E%3C/svg%3E");
  }
}

@layer ui.components {
  /* ===========================================================================
  TEXT INPUTS — data-ui="input"
  - One identity for every text-like type (text, search, email, number, date, …);
    the type only changes the picker/keyboard, not the box.
  - Shares the --ui-field-* tokens defined in fields.css so it aligns with the select,
    the textarea, and [data-ui~="button"].
  =========================================================================== */

  :where([data-ui~="input"]) {
    /* dual-mode metrics go through the registered typed pair */
    --_w-len: var(--_w-resolved, var(--ui-input-w));
    --_w-num: var(--_w-resolved, var(--ui-input-w));
    inline-size: calc(var(--_w-len) + var(--_w-num) * var(--spacing));
    --_min-w-len: var(--_min-w-resolved, var(--ui-input-min-w));
    --_min-w-num: var(--_min-w-resolved, var(--ui-input-min-w));
    min-inline-size: calc(var(--_min-w-len) + var(--_min-w-num) * var(--spacing));
    --_h-len: var(--_h-resolved, var(--ui-field-h));
    --_h-num: var(--_h-resolved, var(--ui-field-h));
    block-size: calc(var(--_h-len) + var(--_h-num) * var(--spacing));
    --_pl-len: var(--_pl-resolved, var(--ui-input-pl));
    --_pl-num: var(--_pl-resolved, var(--ui-input-pl));
    padding-inline-start: calc(var(--_pl-len) + var(--_pl-num) * var(--spacing));
    --_pr-len: var(--_pr-resolved, var(--ui-input-pr));
    --_pr-num: var(--_pr-resolved, var(--ui-input-pr));
    padding-inline-end: calc(var(--_pr-len) + var(--_pr-num) * var(--spacing));

    font-size: var(--_font-size-resolved, var(--ui-input-font-size));
    line-height: var(--_leading-resolved, var(--ui-input-leading));
    color: var(--_text-resolved, var(--ui-field-text));
    background-color: var(--_bg-resolved, var(--ui-field-bg));

    border-width: var(--_border-width-resolved, var(--ui-input-border-width));
    border-style: var(--_border-style-resolved, var(--ui-input-border-style));
    border-color: var(--_border-color-resolved, var(--ui-input-border-color));
    border-radius: var(--_rounded-resolved, var(--ui-input-rounded));

    /* The ring is published to --_focus-shadow so a --shadow utility keeps
       it; the transparent outline twin keeps focus visible in forced-colors */
    --_ring-offset-width: 0px;
    --_ring-width: 0px;
    --_focus-in: max(0, -1 * sign(var(--_focus-ring-w, 1px)));
    --_ring-fill: color-mix(in oklch, var(--ui-field-ring-color) var(--ring-opacity), transparent);
    --_focus-shadow:
      0 0 0 calc(var(--_ring-offset-width) * (1 - var(--_focus-in))) var(--ring-offset-shadow-color),
      0 0 0 calc((var(--_ring-offset-width) + var(--_ring-width)) * (1 - var(--_focus-in)))
        var(--_ring-fill, var(--ring-shadow-color)),
      inset 0 0 0 calc(var(--_ring-offset-width) * var(--_focus-in)) var(--ring-offset-shadow-color),
      inset 0 0 0 calc((var(--_ring-offset-width) + var(--_ring-width)) * var(--_focus-in))
        var(--_ring-fill, var(--ring-shadow-color));
    box-shadow: var(--_focus-shadow, 0 0 #0000), var(--_shadow-resolved, 0 0 #0000);
    outline: abs(var(--_focus-ring-w, var(--outline-width))) var(--outline-style) transparent;
    outline-offset: calc(
      var(--outline-offset) - var(--_focus-in) *
        (2 * var(--outline-offset) + abs(var(--_focus-ring-w, var(--outline-width))))
    );

    transition: var(--default-transition);
  }

  @media (hover: hover) {
    :where([data-ui~="input"]):hover {
      border-color: var(--_border-color-resolved, var(--ui-field-border-color--hover));
      background-color: var(--_bg-resolved, var(--ui-field-bg--hover));
    }
  }

  :where([data-ui~="input"]):focus-within {
    background-color: var(--_bg-resolved, var(--ui-field-bg--focus-within));
    border-color: var(--_border-color-resolved, var(--ui-field-border-color--focus-within));
    --_ring-offset-width: var(--ring-offset-width);
    --_ring-width: abs(var(--_focus-ring-w, var(--ring-width)));
    outline-color: transparent;
  }

  :where([data-ui~="input"])[type="date"],
  :where([data-ui~="input"])[type="time"],
  :where([data-ui~="input"])[type="datetime-local"] {
    --ui-input-pr: calc(var(--spacing) * 1.5);
  }

  /* Native date/time controls keep their picker indicator tinted to the text */
  :where([data-ui~="input"])::-webkit-calendar-picker-indicator {
    cursor: pointer;
    -webkit-appearance: none;

    margin: 0;
    padding: 0;
    inline-size: var(--ui-input-calendar-picker-size);
    block-size: var(--ui-input-calendar-picker-size);
    border-radius: var(--ui-input-calendar-picker-radius);

    transition:
      opacity var(--default-transition-duration) var(--default-transition-timing-function),
      display var(--default-transition-duration) var(--default-transition-timing-function)
        allow-discrete;

    mask: var(--ui-input-calendar-picker-mask) no-repeat center /
      var(--ui-input-calendar-picker-icon-size);
    background-size: var(--ui-input-calendar-picker-icon-size);
    background-color: var(--ui-input-calendar-picker-color);
    background-repeat: no-repeat;
    background-position: center;
  }

  :where([data-ui~="input"])::-webkit-calendar-picker-indicator:is(:hover) {
    opacity: 0.5;
  }
}

/* Disabled and committed-invalid states; an invalid control remaps the
   --ui-field-* family on itself. */
@layer ui.components {
  :where([data-ui~="input"]):disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }

  :where([data-ui~="input"]):user-invalid {
    --ui-field-bg: color-mix(in oklch, var(--color-destructive) 5%, var(--color-input));
    --ui-field-bg--hover: color-mix(in oklch, var(--color-destructive) 10%, var(--color-input));
    --ui-field-border-color: var(--color-destructive);
    --ui-field-border-color--hover: var(--color-destructive);
    --ui-field-border-color--focus-within: var(--color-destructive);
    --ui-field-ring-color: var(--color-destructive);

    border-color: var(--color-destructive);
  }
}

Icon leading

<label data-ui="input-group">
  <input data-ui="input" type="text" name="query" placeholder="Placeholder" />
  <span data-input-group-slot="addon">
    <svg
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
      aria-hidden="true"
    >
      <line x1="4" y1="6" x2="20" y2="6" />
      <line x1="4" y1="12" x2="20" y2="12" />
      <line x1="4" y1="18" x2="20" y2="18" />
    </svg>
  </span>
</label>
/**
 * input.css — Text input ([data-ui~="input"])
 *
 * @layer      variables, components
 * @requires   _layers.css, _variables.css, _properties.css (--_*-resolved, --_focus-shadow), fields.css, _reset.css
 * @uses       ::-webkit-calendar-picker-indicator — date/time picker chrome (WebKit)
 * @uses       mask — calendar icon (not background-image, for color: currentColor)
 * @tokens     --ui-input-* (@layer variables; surface colors and height read the
 *             --ui-field-* family directly)
 */

@layer variables {
  :root {
    --ui-input-w: 100%;
    --ui-input-min-w: 16ch;
    --ui-input-pl: var(--ui-field-px);
    --ui-input-pr: var(--ui-field-px);
    --ui-input-font-size: var(--ui-field-font-size);
    --ui-input-leading: var(--ui-field-leading);
    --ui-input-border-width: var(--ui-field-border-width);
    --ui-input-border-style: var(--ui-field-border-style);
    --ui-input-border-color: var(--ui-field-border-color);
    --ui-input-rounded: var(--ui-field-rounded);

    --ui-input-calendar-picker-color: currentColor;
    --ui-input-calendar-picker-icon-size: calc(var(--spacing) * 4);
    --ui-input-calendar-picker-size: calc(var(--spacing) * 6);
    --ui-input-calendar-picker-radius: var(--ui-field-inset-child-radius);
    --ui-input-calendar-picker-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Crect width='256' height='256' fill='none'/%3E%3Crect x='40' y='40' width='176' height='176' rx='8' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='16'/%3E%3Cline x1='176' y1='24' x2='176' y2='56' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='16'/%3E%3Cline x1='80' y1='24' x2='80' y2='56' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='16'/%3E%3Cline x1='40' y1='88' x2='216' y2='88' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='16'/%3E%3C/svg%3E");
  }
}

@layer ui.components {
  /* ===========================================================================
  TEXT INPUTS — data-ui="input"
  - One identity for every text-like type (text, search, email, number, date, …);
    the type only changes the picker/keyboard, not the box.
  - Shares the --ui-field-* tokens defined in fields.css so it aligns with the select,
    the textarea, and [data-ui~="button"].
  =========================================================================== */

  :where([data-ui~="input"]) {
    /* dual-mode metrics go through the registered typed pair */
    --_w-len: var(--_w-resolved, var(--ui-input-w));
    --_w-num: var(--_w-resolved, var(--ui-input-w));
    inline-size: calc(var(--_w-len) + var(--_w-num) * var(--spacing));
    --_min-w-len: var(--_min-w-resolved, var(--ui-input-min-w));
    --_min-w-num: var(--_min-w-resolved, var(--ui-input-min-w));
    min-inline-size: calc(var(--_min-w-len) + var(--_min-w-num) * var(--spacing));
    --_h-len: var(--_h-resolved, var(--ui-field-h));
    --_h-num: var(--_h-resolved, var(--ui-field-h));
    block-size: calc(var(--_h-len) + var(--_h-num) * var(--spacing));
    --_pl-len: var(--_pl-resolved, var(--ui-input-pl));
    --_pl-num: var(--_pl-resolved, var(--ui-input-pl));
    padding-inline-start: calc(var(--_pl-len) + var(--_pl-num) * var(--spacing));
    --_pr-len: var(--_pr-resolved, var(--ui-input-pr));
    --_pr-num: var(--_pr-resolved, var(--ui-input-pr));
    padding-inline-end: calc(var(--_pr-len) + var(--_pr-num) * var(--spacing));

    font-size: var(--_font-size-resolved, var(--ui-input-font-size));
    line-height: var(--_leading-resolved, var(--ui-input-leading));
    color: var(--_text-resolved, var(--ui-field-text));
    background-color: var(--_bg-resolved, var(--ui-field-bg));

    border-width: var(--_border-width-resolved, var(--ui-input-border-width));
    border-style: var(--_border-style-resolved, var(--ui-input-border-style));
    border-color: var(--_border-color-resolved, var(--ui-input-border-color));
    border-radius: var(--_rounded-resolved, var(--ui-input-rounded));

    /* The ring is published to --_focus-shadow so a --shadow utility keeps
       it; the transparent outline twin keeps focus visible in forced-colors */
    --_ring-offset-width: 0px;
    --_ring-width: 0px;
    --_focus-in: max(0, -1 * sign(var(--_focus-ring-w, 1px)));
    --_ring-fill: color-mix(in oklch, var(--ui-field-ring-color) var(--ring-opacity), transparent);
    --_focus-shadow:
      0 0 0 calc(var(--_ring-offset-width) * (1 - var(--_focus-in))) var(--ring-offset-shadow-color),
      0 0 0 calc((var(--_ring-offset-width) + var(--_ring-width)) * (1 - var(--_focus-in)))
        var(--_ring-fill, var(--ring-shadow-color)),
      inset 0 0 0 calc(var(--_ring-offset-width) * var(--_focus-in)) var(--ring-offset-shadow-color),
      inset 0 0 0 calc((var(--_ring-offset-width) + var(--_ring-width)) * var(--_focus-in))
        var(--_ring-fill, var(--ring-shadow-color));
    box-shadow: var(--_focus-shadow, 0 0 #0000), var(--_shadow-resolved, 0 0 #0000);
    outline: abs(var(--_focus-ring-w, var(--outline-width))) var(--outline-style) transparent;
    outline-offset: calc(
      var(--outline-offset) - var(--_focus-in) *
        (2 * var(--outline-offset) + abs(var(--_focus-ring-w, var(--outline-width))))
    );

    transition: var(--default-transition);
  }

  @media (hover: hover) {
    :where([data-ui~="input"]):hover {
      border-color: var(--_border-color-resolved, var(--ui-field-border-color--hover));
      background-color: var(--_bg-resolved, var(--ui-field-bg--hover));
    }
  }

  :where([data-ui~="input"]):focus-within {
    background-color: var(--_bg-resolved, var(--ui-field-bg--focus-within));
    border-color: var(--_border-color-resolved, var(--ui-field-border-color--focus-within));
    --_ring-offset-width: var(--ring-offset-width);
    --_ring-width: abs(var(--_focus-ring-w, var(--ring-width)));
    outline-color: transparent;
  }

  :where([data-ui~="input"])[type="date"],
  :where([data-ui~="input"])[type="time"],
  :where([data-ui~="input"])[type="datetime-local"] {
    --ui-input-pr: calc(var(--spacing) * 1.5);
  }

  /* Native date/time controls keep their picker indicator tinted to the text */
  :where([data-ui~="input"])::-webkit-calendar-picker-indicator {
    cursor: pointer;
    -webkit-appearance: none;

    margin: 0;
    padding: 0;
    inline-size: var(--ui-input-calendar-picker-size);
    block-size: var(--ui-input-calendar-picker-size);
    border-radius: var(--ui-input-calendar-picker-radius);

    transition:
      opacity var(--default-transition-duration) var(--default-transition-timing-function),
      display var(--default-transition-duration) var(--default-transition-timing-function)
        allow-discrete;

    mask: var(--ui-input-calendar-picker-mask) no-repeat center /
      var(--ui-input-calendar-picker-icon-size);
    background-size: var(--ui-input-calendar-picker-icon-size);
    background-color: var(--ui-input-calendar-picker-color);
    background-repeat: no-repeat;
    background-position: center;
  }

  :where([data-ui~="input"])::-webkit-calendar-picker-indicator:is(:hover) {
    opacity: 0.5;
  }
}

/* Disabled and committed-invalid states; an invalid control remaps the
   --ui-field-* family on itself. */
@layer ui.components {
  :where([data-ui~="input"]):disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }

  :where([data-ui~="input"]):user-invalid {
    --ui-field-bg: color-mix(in oklch, var(--color-destructive) 5%, var(--color-input));
    --ui-field-bg--hover: color-mix(in oklch, var(--color-destructive) 10%, var(--color-input));
    --ui-field-border-color: var(--color-destructive);
    --ui-field-border-color--hover: var(--color-destructive);
    --ui-field-border-color--focus-within: var(--color-destructive);
    --ui-field-ring-color: var(--color-destructive);

    border-color: var(--color-destructive);
  }
}

Icon trailing

<label data-ui="input-group">
  <input data-ui="input" type="search" name="q" placeholder="Search…" aria-label="Search" />
  <span data-input-group-slot="addon" data-input-group-align="inline-end">
    <svg
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
      aria-hidden="true"
    >
      <circle cx="11" cy="11" r="8" />
      <line x1="21" y1="21" x2="16.65" y2="16.65" />
    </svg>
  </span>
</label>
/**
 * input.css — Text input ([data-ui~="input"])
 *
 * @layer      variables, components
 * @requires   _layers.css, _variables.css, _properties.css (--_*-resolved, --_focus-shadow), fields.css, _reset.css
 * @uses       ::-webkit-calendar-picker-indicator — date/time picker chrome (WebKit)
 * @uses       mask — calendar icon (not background-image, for color: currentColor)
 * @tokens     --ui-input-* (@layer variables; surface colors and height read the
 *             --ui-field-* family directly)
 */

@layer variables {
  :root {
    --ui-input-w: 100%;
    --ui-input-min-w: 16ch;
    --ui-input-pl: var(--ui-field-px);
    --ui-input-pr: var(--ui-field-px);
    --ui-input-font-size: var(--ui-field-font-size);
    --ui-input-leading: var(--ui-field-leading);
    --ui-input-border-width: var(--ui-field-border-width);
    --ui-input-border-style: var(--ui-field-border-style);
    --ui-input-border-color: var(--ui-field-border-color);
    --ui-input-rounded: var(--ui-field-rounded);

    --ui-input-calendar-picker-color: currentColor;
    --ui-input-calendar-picker-icon-size: calc(var(--spacing) * 4);
    --ui-input-calendar-picker-size: calc(var(--spacing) * 6);
    --ui-input-calendar-picker-radius: var(--ui-field-inset-child-radius);
    --ui-input-calendar-picker-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Crect width='256' height='256' fill='none'/%3E%3Crect x='40' y='40' width='176' height='176' rx='8' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='16'/%3E%3Cline x1='176' y1='24' x2='176' y2='56' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='16'/%3E%3Cline x1='80' y1='24' x2='80' y2='56' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='16'/%3E%3Cline x1='40' y1='88' x2='216' y2='88' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='16'/%3E%3C/svg%3E");
  }
}

@layer ui.components {
  /* ===========================================================================
  TEXT INPUTS — data-ui="input"
  - One identity for every text-like type (text, search, email, number, date, …);
    the type only changes the picker/keyboard, not the box.
  - Shares the --ui-field-* tokens defined in fields.css so it aligns with the select,
    the textarea, and [data-ui~="button"].
  =========================================================================== */

  :where([data-ui~="input"]) {
    /* dual-mode metrics go through the registered typed pair */
    --_w-len: var(--_w-resolved, var(--ui-input-w));
    --_w-num: var(--_w-resolved, var(--ui-input-w));
    inline-size: calc(var(--_w-len) + var(--_w-num) * var(--spacing));
    --_min-w-len: var(--_min-w-resolved, var(--ui-input-min-w));
    --_min-w-num: var(--_min-w-resolved, var(--ui-input-min-w));
    min-inline-size: calc(var(--_min-w-len) + var(--_min-w-num) * var(--spacing));
    --_h-len: var(--_h-resolved, var(--ui-field-h));
    --_h-num: var(--_h-resolved, var(--ui-field-h));
    block-size: calc(var(--_h-len) + var(--_h-num) * var(--spacing));
    --_pl-len: var(--_pl-resolved, var(--ui-input-pl));
    --_pl-num: var(--_pl-resolved, var(--ui-input-pl));
    padding-inline-start: calc(var(--_pl-len) + var(--_pl-num) * var(--spacing));
    --_pr-len: var(--_pr-resolved, var(--ui-input-pr));
    --_pr-num: var(--_pr-resolved, var(--ui-input-pr));
    padding-inline-end: calc(var(--_pr-len) + var(--_pr-num) * var(--spacing));

    font-size: var(--_font-size-resolved, var(--ui-input-font-size));
    line-height: var(--_leading-resolved, var(--ui-input-leading));
    color: var(--_text-resolved, var(--ui-field-text));
    background-color: var(--_bg-resolved, var(--ui-field-bg));

    border-width: var(--_border-width-resolved, var(--ui-input-border-width));
    border-style: var(--_border-style-resolved, var(--ui-input-border-style));
    border-color: var(--_border-color-resolved, var(--ui-input-border-color));
    border-radius: var(--_rounded-resolved, var(--ui-input-rounded));

    /* The ring is published to --_focus-shadow so a --shadow utility keeps
       it; the transparent outline twin keeps focus visible in forced-colors */
    --_ring-offset-width: 0px;
    --_ring-width: 0px;
    --_focus-in: max(0, -1 * sign(var(--_focus-ring-w, 1px)));
    --_ring-fill: color-mix(in oklch, var(--ui-field-ring-color) var(--ring-opacity), transparent);
    --_focus-shadow:
      0 0 0 calc(var(--_ring-offset-width) * (1 - var(--_focus-in))) var(--ring-offset-shadow-color),
      0 0 0 calc((var(--_ring-offset-width) + var(--_ring-width)) * (1 - var(--_focus-in)))
        var(--_ring-fill, var(--ring-shadow-color)),
      inset 0 0 0 calc(var(--_ring-offset-width) * var(--_focus-in)) var(--ring-offset-shadow-color),
      inset 0 0 0 calc((var(--_ring-offset-width) + var(--_ring-width)) * var(--_focus-in))
        var(--_ring-fill, var(--ring-shadow-color));
    box-shadow: var(--_focus-shadow, 0 0 #0000), var(--_shadow-resolved, 0 0 #0000);
    outline: abs(var(--_focus-ring-w, var(--outline-width))) var(--outline-style) transparent;
    outline-offset: calc(
      var(--outline-offset) - var(--_focus-in) *
        (2 * var(--outline-offset) + abs(var(--_focus-ring-w, var(--outline-width))))
    );

    transition: var(--default-transition);
  }

  @media (hover: hover) {
    :where([data-ui~="input"]):hover {
      border-color: var(--_border-color-resolved, var(--ui-field-border-color--hover));
      background-color: var(--_bg-resolved, var(--ui-field-bg--hover));
    }
  }

  :where([data-ui~="input"]):focus-within {
    background-color: var(--_bg-resolved, var(--ui-field-bg--focus-within));
    border-color: var(--_border-color-resolved, var(--ui-field-border-color--focus-within));
    --_ring-offset-width: var(--ring-offset-width);
    --_ring-width: abs(var(--_focus-ring-w, var(--ring-width)));
    outline-color: transparent;
  }

  :where([data-ui~="input"])[type="date"],
  :where([data-ui~="input"])[type="time"],
  :where([data-ui~="input"])[type="datetime-local"] {
    --ui-input-pr: calc(var(--spacing) * 1.5);
  }

  /* Native date/time controls keep their picker indicator tinted to the text */
  :where([data-ui~="input"])::-webkit-calendar-picker-indicator {
    cursor: pointer;
    -webkit-appearance: none;

    margin: 0;
    padding: 0;
    inline-size: var(--ui-input-calendar-picker-size);
    block-size: var(--ui-input-calendar-picker-size);
    border-radius: var(--ui-input-calendar-picker-radius);

    transition:
      opacity var(--default-transition-duration) var(--default-transition-timing-function),
      display var(--default-transition-duration) var(--default-transition-timing-function)
        allow-discrete;

    mask: var(--ui-input-calendar-picker-mask) no-repeat center /
      var(--ui-input-calendar-picker-icon-size);
    background-size: var(--ui-input-calendar-picker-icon-size);
    background-color: var(--ui-input-calendar-picker-color);
    background-repeat: no-repeat;
    background-position: center;
  }

  :where([data-ui~="input"])::-webkit-calendar-picker-indicator:is(:hover) {
    opacity: 0.5;
  }
}

/* Disabled and committed-invalid states; an invalid control remaps the
   --ui-field-* family on itself. */
@layer ui.components {
  :where([data-ui~="input"]):disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }

  :where([data-ui~="input"]):user-invalid {
    --ui-field-bg: color-mix(in oklch, var(--color-destructive) 5%, var(--color-input));
    --ui-field-bg--hover: color-mix(in oklch, var(--color-destructive) 10%, var(--color-input));
    --ui-field-border-color: var(--color-destructive);
    --ui-field-border-color--hover: var(--color-destructive);
    --ui-field-border-color--focus-within: var(--color-destructive);
    --ui-field-ring-color: var(--color-destructive);

    border-color: var(--color-destructive);
  }
}

Attributes

input attributes
AttributeKindValuesNotes
data-ui="input"Identityfree valueIdentity token in data-ui.

Requires: fields.

Hooks

Set a hook on :root, on a subtree, or inline on one element. A state hook (--…--hover) themes that state only; the primitive keeps its other states.

input hooks
HookDefaultState
--ui-input-border-colorvar(--ui-field-border-color)—
--ui-input-border-stylevar(--ui-field-border-style)—
--ui-input-border-widthvar(--ui-field-border-width)—
--ui-input-calendar-picker-colorcurrentColor—
--ui-input-calendar-picker-icon-sizecalc(var(--spacing) * 4)—
--ui-input-calendar-picker-maskurl("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Crect width='256' height='256' fill='none'/%3E%3Crect x='40' y='40' width='176' height='176' rx='8' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='16'/%3E%3Cline x1='176' y1='24' x2='176' y2='56' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='16'/%3E%3Cline x1='80' y1='24' x2='80' y2='56' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='16'/%3E%3Cline x1='40' y1='88' x2='216' y2='88' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='16'/%3E%3C/svg%3E")—
--ui-input-calendar-picker-radiusvar(--ui-field-inset-child-radius)—
--ui-input-calendar-picker-sizecalc(var(--spacing) * 6)—
--ui-input-font-sizevar(--ui-field-font-size)—
--ui-input-leadingvar(--ui-field-leading)—
--ui-input-min-w16ch—
--ui-input-plvar(--ui-field-px)—
--ui-input-prvar(--ui-field-px)—
--ui-input-roundedvar(--ui-field-rounded)—
--ui-input-w100%—