Skip to main content

Theme config

Theme mode

Color palette

Grays

Border radii/radiuses/radiopedes/you know
Border radius
Field border radius
Button border radius
All
Components
Guides
API
Recent

Components

Text field

An input for single-line text data. Basic text fields with labels, field descriptions, and validation states.

Full support Supported since v125. Partial support Missing: field-sizing. Full support Supported since v26.2.

Variants

<label class="text-field">
<span class="label">Outlined</span>
<span class="field">
<input type="text" placeholder="Placeholder" />
</span>
</label>
<label class="text-field filled">
<span class="label">Filled</span>
<span class="field">
<input type="text" placeholder="Placeholder" />
</span>
</label>

Sizes

<label class="text-field small">
<span class="label">Small outlined</span>
<span class="field">
<input type="text" placeholder="Placeholder" />
</span>
</label>
<label class="text-field filled small">
<span class="label">Small filled</span>
<span class="field">
<input type="text" placeholder="Placeholder" />
</span>
</label>

End text

<label class="text-field">
<span class="label">Label</span>
<span class="field">
<input type="text" placeholder="Outlined" />
</span>
<span class="end-text">Supporting text</span>
</label>

Affix

Add .prefix, .suffix, .header, or .footer elements inside .field to affix content inside the field's border. Prefix and suffix sit beside the input, while header and footer span the field's full width with a divider.

<label class="text-field">
<span class="label">Amount</span>
<span class="field">
<input type="text" placeholder="0.00" />
<span class="prefix">¢</span>
<span class="suffix">EUR</span>
</span>
</label>
<label class="text-field">
<span class="label">Website</span>
<span class="field">
<input type="text" placeholder="example.com" />
<span class="prefix">https://</span>
</span>
</label>
<label class="text-field">
<span class="label">Weight</span>
<span class="field">
<input type="text" inputmode="numeric" pattern="[0-9]*" placeholder="0" />
<span class="suffix">kg</span>
</span>
</label>
<label class="text-field">
<span class="label">Search</span>
<span class="field">
<input type="text" placeholder="Search..." />
<span class="prefix">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.3-4.3"></path>
</svg>
</span>
</span>
</label>

Headers and footers

Use the header slot for inside-field captions (filenames, categories) and the footer slot for counters, hints, or action buttons.

<label class="text-field">
<span class="label">Username</span>
<span class="field">
<input type="text" placeholder="Enter your name" />
<span class="header">Full Name</span>
</span>
</label>
<label class="text-field">
<span class="label">Tagline</span>
<span class="field">
<input type="text" placeholder="A short description" />
<span class="footer">0 / 80</span>
</span>
</label>

Validation

Add [required] to the <input> element to toggle required styles

The .critical class toggles the error styles. Make use of the end text to give extra feedback on the error.

<div class="example-row">
<label class="text-field">
<span class="label">I'm required</span>
<span class="field">
<input type="text" placeholder="Placeholder" required />
</span>
</label>
<label class="text-field filled">
<span class="label">So am I!</span>
<span class="field">
<input type="text" placeholder="Placeholder" required />
</span>
</label>
</div>
<div class="example-row">
<label class="text-field" data-invalid>
<span class="label">Label</span>
<span class="field">
<input type="text" placeholder="Placeholder" value="This isn't right" />
</span>
<span class="end-text">Only double-negatives are allowed.</span>
</label>
<label class="text-field filled" data-invalid>
<span class="label">Label</span>
<span class="field">
<input type="text" placeholder="Placeholder" value="Uh-oh" />
</span>
<span class="end-text">Only letters from the first half of the alphabet are allowed.</span>
</label>
</div>

Spread

Add the .spread class to display the label and description on the left with the input on the right. The layout collapses to a column on narrow containers.

<label class="text-field spread">
<span class="label">Name</span>
<span class="start-text">Provide your full name for identification</span>
<span class="field">
<input type="text" placeholder="Evil Rabbit" />
</span>
</label>
<label class="text-field spread filled">
<span class="label">Email</span>
<span class="start-text">We'll use this to contact you</span>
<span class="field">
<input type="email" placeholder="you@example.com" />
</span>
<span class="end-text">Please use a valid email address</span>
</label>
<label class="text-field spread">
<span class="label">Required</span>
<span class="start-text">You must fill this in</span>
<span class="field">
<input type="text" required />
</span>
</label>
<label class="text-field spread">
<span class="label">Disabled</span>
<span class="start-text">This field is disabled</span>
<span class="field">
<input type="text" disabled />
</span>
</label>
<label class="text-field spread" data-invalid>
<span class="label">Invalid Name</span>
<span class="start-text">This field has an error</span>
<span class="field">
<input type="text" />
</span>
<span class="end-text">This value is too short.</span>
</label>
<label class="text-field spread">
<span class="label">Amount</span>
<span class="start-text">Daily spending limit</span>
<span class="field">
<input type="text" placeholder="0.00" />
<span class="prefix">¢</span>
<span class="suffix">EUR</span>
</span>
</label>
<label class="text-field spread filled">
<span class="label">Website</span>
<span class="start-text">Your public profile URL</span>
<span class="field">
<input type="text" placeholder="example.com" />
<span class="prefix">https://</span>
</span>
<span class="end-text">Must include a valid domain</span>
</label>
<label class="text-field spread">
<span class="label">Project name</span>
<span class="start-text">Used to generate the project URL</span>
<span class="field">
<input type="text" placeholder="my-project" />
<span class="header">acme.dev/</span>
<span class="footer">Lowercase letters and dashes only</span>
</span>
</label>
<label class="text-field spread filled">
<span class="label">API key</span>
<span class="start-text">Stored encrypted at rest</span>
<span class="field">
<input type="password" placeholder="Paste your key" />
<span class="prefix">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<rect width="18" height="11" x="3" y="11" rx="2" ry="2"></rect>
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
</svg>
</span>
<span class="header">Secret</span>
<span class="footer">Rotates every 90 days</span>
</span>
<span class="end-text">Treat like a password</span>
</label>

Auto-fit

When enabled the Field changes size depending on its content.

<label class="text-field auto-fit">
<span class="label">Label</span>
<span class="field">
<input type="text" placeholder="Auto-fit" />
</span>
</label>

Input types

<div class="example-column">
<label class="text-field input-type-field">
<span class="label">Color</span>
<span class="field">
<input type="color" placeholder="Color" />
</span>
</label>
<label class="text-field input-type-field">
<span class="label">Email</span>
<span class="field">
<input type="email" placeholder="name@email.com" />
</span>
</label>
<label class="text-field input-type-field">
<span class="label">Password</span>
<span class="field">
<input type="password" placeholder="Password" />
</span>
</label>
<label class="text-field input-type-field">
<span class="label">Search</span>
<span class="field">
<input type="search" placeholder="Search" />
</span>
</label>
<label class="text-field input-type-field">
<span class="label">Phone</span>
<span class="field">
<input type="tel" placeholder="(666) 666-1337" />
</span>
</label>
<label class="text-field input-type-field">
<span class="label">Text</span>
<span class="field">
<input type="text" placeholder="Text" />
</span>
</label>
<label class="text-field input-type-field">
<span class="label">URL</span>
<span class="field">
<input type="url" placeholder="https://yoursite.com" />
</span>
</label>
</div>
<div class="example-column">
<label class="text-field input-type-field">
<span class="label">Date</span>
<span class="field">
<input type="date" placeholder="Date" />
</span>
</label>
<label class="text-field input-type-field">
<span class="label">Datetime local</span>
<span class="field">
<input type="datetime-local" placeholder="Datetime local" />
</span>
</label>
<label class="text-field input-type-field">
<span class="label">Month</span>
<span class="field">
<input type="month" placeholder="Month" />
</span>
</label>
<label class="text-field input-type-field">
<span class="label">Time</span>
<span class="field">
<input type="time" placeholder="Time" />
</span>
</label>
<label class="text-field input-type-field">
<span class="label">Week</span>
<span class="field">
<input type="week" placeholder="Week" />
</span>
</label>
</div>

Date inputs

Date-related inputs never show as empty, so the label is always visible. There are only hacks with compromises and no neat ways of dealing with that issue. You're free to implement a solution of your own here that works with your project.

Numeric vs <input type="number">

<label class="text-field">
<span class="label">Numeric</span>
<span class="field">
<input type="text" inputmode="numeric" pattern="[0-9]*" placeholder="Numeric" />
</span>
</label>

You most likely don't need <input type="number">

While <input type="number"> may seem logical for numeric data it should only be used when mathematical operations are needed on the input (which is... never). Data like credit card numbers, IDs or social security numbers - are actually text that happen to be numeric rather than mathematical values. Therefore, consider using <input type="text" inputmode="numeric" pattern="[0-9]*"> instead.

You will have a bad time.

This triggers the numeric keyboard on mobile devices while avoiding the jank of number inputs, such as:

  • Unexpected value increments from scroll wheels
  • Browser-specific validation differences
  • Accessibility problems
  • Removal of leading zeros
  • Allows for some non-numeric mathematical characters

It should probably be called <input type="math"> instead.

The British Government has a great article about how bad input number is and goes in-depth. It's a very interesting read.

File

Use aria-label instead of the <label> element.

File is a weird one. Should it really be an <input> element? Well, it's what we've got :sweat_smile:

<div class="text-field" aria-label="Label">
<span class="field">
<input type="file" placeholder="File" />
</span>
</div>
<div class="text-field filled" aria-label="Label">
<span class="field">
<input type="file" placeholder="File" />
</span>
</div>

Autosuggest

Leverages the <input> + <datalist> element combo.

  • <input list="DATALISTID">
  • <datalist id="DATALISTID">
<label class="text-field">
<span class="label">Users</span>
<span class="field">
<input type="text" list="users" placeholder="Placeholder" />
</span>
<datalist id="users">
<option value="Ray Manzarek"></option>
<option value="Jonny Greenwood"></option>
<option value="Marika Hackman"></option>
</datalist>
</label>
<label class="text-field filled">
<span class="label">Emails</span>
<span class="field">
<input type="email" list="users-email" placeholder="Placeholder" />
</span>
<datalist id="users-email">
<option value="ray.manzarek@the.doors"></option>
<option value="jonny.greenwood@radio.head"></option>
<option value="marika@hack.man"></option>
</datalist>
</label>

Think of <datalist> as a list of suggested values.

  • <select> only allows you to choose between its provided values.
  • <input> lets you input anything you want.
  • <input> + <datalist> is a hybrid between the two.

Do I have to use <label>?

No. But you get some accessibility wins for free with <label>. It's recommended to label your inputs somehow.

<div class="text-field">
<span class="field">
<input type="text" placeholder="Placeholder" />
</span>
</div>

Accessibility

Anatomy

  1. label.text-field: Container element
  2. .label: Field label element
  3. .field: The boxed input area
  4. .header: Optional inside-border header strip (with divider)
  5. .prefix: Optional inline-start affix
  6. <input>: Input element
  7. .suffix: Optional inline-end affix
  8. .footer: Optional inside-border footer strip (with divider)
  9. .end-text: Supporting text element

API

Text field API

Type Modifiers Default Description
Auto-fit .auto-fit - When enabled, the element changes size depending on its content.
Children .label, .start-text, .field, <datalist>, .end-text - Direct children of the root element.
Field children <input>, <select>, <textarea>, .prefix, .suffix, .header, .footer - Children of .field. The control element comes first, then optional affixes.
Layout .spread, default - The layout of the component. .spread pushes label and description to the left and input to the right.
Sizes .small - The size of the element.
Validation [data-invalid] - Add the data-invalid attribute to the root element to show error styles.
Variants default, .filled - The variant to use.

Text input API

Type Modifiers Default Description
Wrapper .field - The <input> must be wrapped in a <span class="field"> element. Border, background, and focus styling are inherited from .field, not the input itself.
Auto-fit .auto-fit - When enabled, the element changes size depending on its content.
Sizes .small - The size of the element.
Variants default, .filled - The variant to use.

Browser support

Full support Supported since v125. Partial support Missing: field-sizing. Full support Supported since v26.2.

See also the full browser support guide.

Installation

@layer components.extended {
/*
- Common styling for input, textarea and select
- Form related styles such as: label, supporting text, error handling
*/
:where(.text-field, .textarea, .select) {
--_accent-color: var(--primary);
--_bg-color: var(--surface-default);
--_border-color: var(--field-border-color);
--_field-padding-block: var(--size-2);
--_field-padding-inline: var(--size-2);
--_filled-border-color: var(--text-primary);
--_height: var(--field-size);
--_label-color: var(--text-muted);
--_end-text-color: var(--text-muted);
display: grid;
gap: var(--size-1) 0;
position: relative;
/* Field - the actual input with a border around it */
.field {
background-color: var(--_bg-color);
border: var(--field-border-width) solid var(--_border-color);
border-radius: var(--field-border-radius);
display: grid;
grid-column: 1/-1;
grid-row: 2;
grid-template-columns: auto 1fr auto;
grid-template-areas:
"header header header"
"prefix input suffix"
"footer footer footer";
min-block-size: var(--_height);
@media (prefers-reduced-motion: no-preference) {
transition: border-color 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
}
:where(.field) :where(input, textarea, select) {
background: transparent;
border: 0;
color: var(--text-primary);
font-family: var(--font-sans);
font-size: var(--font-size-1);
grid-area: input;
inline-size: 100%;
line-height: var(--font-lineheight-1);
min-inline-size: 0;
padding: var(--_field-padding-block) var(--_field-padding-inline);
&:focus,
&:focus-visible {
outline: 0;
}
}
/* Affixes */
.prefix,
.suffix {
align-items: center;
color: var(--_label-color);
display: inline-flex;
padding-inline: var(--_field-padding-inline);
white-space: nowrap;
}
.prefix {
grid-area: prefix;
}
.suffix {
grid-area: suffix;
}
.header,
.footer {
align-items: center;
color: var(--_label-color);
display: flex;
font-size: var(--font-size-0);
gap: var(--size-2);
padding: var(--size-1) var(--_field-padding-inline);
}
.header {
border-block-end: 1px solid var(--_border-color);
grid-area: header;
}
.footer {
border-block-start: 1px solid var(--_border-color);
grid-area: footer;
}
/* Remove padding on the input when affixes are next to it */
.field:has(> .prefix) :where(input, textarea, select) {
padding-inline-start: 0;
}
.field:has(> .suffix) :where(input, textarea, select) {
padding-inline-end: 0;
}
/* Textarea - keep affix aligned with the first line */
&.textarea .field {
.prefix,
.suffix {
align-self: start;
padding-block: var(--_field-padding-block);
}
}
/* Required/Invalid */
&:has(:invalid) {
.label:after {
color: var(--red);
content: "*";
margin: -0.25em auto auto 0.25em;
}
}
/* File */
&:has(input[type="file"]) {
cursor: pointer;
input {
align-self: flex-start;
block-size: var(--_height);
box-shadow: none;
color: var(--text-primary);
cursor: inherit;
max-inline-size: 100%;
padding: 0;
transition: font-size 0.2s var(--ease-3);
&::-webkit-file-upload-button,
&::file-selector-button {
background-color: var(--surface-tonal);
block-size: calc(100% - var(--size-2) * 2);
border: none;
border-radius: var(--field-border-radius);
cursor: pointer;
margin-block-start: var(--size-2);
margin-inline-end: 1ex;
margin-inline-start: var(--size-2);
}
}
/* Variants */
&.filled {
input {
&::-webkit-file-upload-button,
&::file-selector-button {
background-color: var(--surface-default);
block-size: calc(100% - var(--size-2) * 2);
border-radius: var(--field-border-radius);
cursor: pointer;
margin-block-start: var(--size-2);
}
}
}
/* Sizes */
&.small {
input {
font-size: var(--font-size-05);
&::-webkit-file-upload-button,
&::file-selector-button {
block-size: calc(100% - var(--size-2));
margin-block-start: var(--size-1);
}
}
}
}
/* Autosuggest */
&:has(input[list]) {
.label {
/* Make sure chevron is visible */
inline-size: calc(100% - var(--size-6));
}
}
/* Select */
&:has(select) {
.label {
/* Make sure chevron is visible */
inline-size: calc(100% - var(--size-6));
}
}
/* Customizable Select */
&:has(select button) {
select {
padding: 0;
button {
outline: 0;
padding: var(--_field-padding-block) var(--size-8) var(--_field-padding-block) var(--_field-padding-inline);
}
}
}
/* Non-customizable Select */
&:has(select):not(:has(button)) {
select {
padding: var(--_field-padding-block) var(--size-8) var(--_field-padding-block) var(--_field-padding-inline);
}
}
/* Input - color */
&:has(input[type="color"]) {
.field {
block-size: var(--_height);
inline-size: var(--_height);
min-block-size: 0;
overflow: hidden;
}
input {
appearance: none;
background: none;
block-size: var(--_height);
inline-size: var(--_height);
overflow: hidden;
padding: 0;
&::-webkit-color-swatch {
border: none;
}
&::-webkit-color-swatch-wrapper {
padding: 0;
}
}
}
/* Element states */
&:hover {
&:not([data-invalid]) {
.field {
border-color: var(--text-primary);
}
}
}
&:focus-within {
.field {
border-color: var(--_accent-color);
outline-offset: -2px;
}
}
/* Label */
.label {
color: var(--text-primary);
font-size: var(--font-size-05);
font-weight: 600;
grid-column: 1/-1;
grid-row: 1;
padding-inline: 0 1ex;
}
/* End text */
:where(.end-text) {
color: var(--_end-text-color);
font-size: var(--font-size-0);
grid-column: 1/-1;
grid-row: 3;
line-height: 1.5;
}
/* Auto-fit */
&.auto-fit {
inline-size: auto;
.field {
inline-size: auto;
}
:where(& input, & textarea) {
field-sizing: content;
inline-size: auto;
min-inline-size: 25ch;
}
}
/* Validation */
&[data-invalid],
&:has(:user-invalid) {
--_accent-color: var(--color-9);
--_border-color: var(--color-9);
--_filled-border-color: var(--color-9);
--_label-color: var(--color-9);
--_end-text-color: var(--color-9);
}
/*
* Variant: Filled
*/
&.filled {
--_bg-color: var(--surface-tonal);
&:not(:has([disabled], :has(input[type="color"]))) {
/* Hover */
&:hover {
--_bg-color: light-dark(oklch(from var(--surface-tonal) calc(l * 0.93) c h),
oklch(from var(--surface-tonal) calc(l * 1.1) c h));
}
}
}
/* Disabled */
&:where(:has([disabled])) {
cursor: not-allowed;
opacity: 0.64;
user-select: none;
&::before {
display: none;
}
.field {
cursor: not-allowed;
}
:where(input, textarea, select) {
cursor: not-allowed;
* {
pointer-events: none;
}
}
.label,
.start-text,
.end-text {
cursor: not-allowed;
}
}
/* Read-only */
&:where(:has([readonly])) {
&::before {
display: none;
}
:where(input, textarea, select) {
cursor: not-allowed;
* {
pointer-events: none;
}
}
}
/* Sizes */
&.small {
--_field-padding-block: var(--size-2);
--_height: var(--field-size-small);
&:has(input[type="color"]) {
.label {
line-height: 1.5;
}
}
}
/* Orientation */
&.spread {
align-items: start;
column-gap: var(--size-4);
container-type: inline-size;
grid-template-columns: 1fr auto;
.label {
font-weight: 600;
grid-column: 1;
grid-row: 1;
}
.start-text {
color: var(--_end-text-color);
font-size: var(--font-size-0);
grid-column: 1;
grid-row: 2;
line-height: 1.5;
}
.field {
align-self: start;
grid-column: 2;
grid-row: 1 / span 2;
}
.field:has(textarea) {
grid-row: 1 / span 3;
+.end-text {
grid-row: 4;
}
}
:where(textarea) {
min-inline-size: 30ch;
}
:where(.end-text) {
grid-column: 2;
text-align: end;
}
/* Collapse to column on narrow containers */
@container (width < 400px) {
column-gap: 0;
grid-template-columns: 1fr;
.label {
grid-column: 1;
grid-row: 1;
}
.start-text {
grid-column: 1;
grid-row: 2;
}
.field,
.field:has(textarea) {
grid-column: 1;
grid-row: 3;
}
:where(.end-text) {
grid-column: 1;
grid-row: 4;
text-align: start;
}
}
}
}
}
@layer components.extended {
:where(.text-field:has( :where(input[type="date"],
input[type="datetime-local"],
input[type="email"],
input[type="month"],
input[type="number"],
input[type="password"],
input[type="search"],
input[type="tel"],
input[type="text"],
input[type="time"],
input[type="url"],
input[type="week"]))) {
/* Sizes */
&.small {
input {
padding-inline: var(--size-2);
}
}
}
/* Autosuggest */
:where(.text-field:has(input[list])) {
.field {
position: relative;
}
/* Hide native arrow */
input[list]::-webkit-calendar-picker-indicator {
cursor: pointer;
opacity: 0;
pointer-events: none;
position: absolute;
}
input[list] {
padding-inline-end: var(--size-8);
}
.field::after {
block-size: 0;
border-block-start: 5px solid;
border-inline: 5px solid transparent;
content: "";
inline-size: 0;
inset: 50% var(--size-3) auto auto;
pointer-events: none;
position: absolute;
translate: 0 -50%;
}
&.small {
input[list] {
padding-inline-end: var(--size-7);
}
.field::after {
inset-inline-end: var(--size-2);
}
}
}
}