Skip to content

Text input

Text fields

Lets people enter text: filled and outlined, with labels, icons, prefixes and suffixes, counters and multiple lines.

Examples

Filled and outlined

The label floats inside the field (filled), in the outline's notch (outlined) or above it (labelPosition="above").

The live demo runs with JavaScript. Its code is below.

React HTML
import { TextField } from '@kyralabs/m3exp-web/react';

export default function Variants() {
  return (
    <div style={{ display: 'grid', gap: 'var(--m3e-sys-space-300)', inlineSize: '100%' }}>
      <TextField variant="filled" label="First name" supportingText="As on your passport" />
      <TextField variant="outlined" label="Last name" />
      <TextField variant="outlined" label="Nickname" labelPosition="above" placeholder="Optional" />
    </div>
  );
}
<div style="display: grid; gap: var(--m3e-sys-space-300); inline-size: 100%">
  <m3e-text-field
    variant="filled"
    label="First name"
    supporting-text="As on your passport"
  ></m3e-text-field>
  <m3e-text-field variant="outlined" label="Last name"></m3e-text-field>
  <m3e-text-field
    variant="outlined"
    label="Nickname"
    label-position="above"
    placeholder="Optional"
  ></m3e-text-field>
</div>

Run the HTML version (custom elements, no framework).

Errors, affixes and counters

An error message that replaces the supporting text and is announced, a prefix and suffix, a multiline field with a live character counter, and a disabled field.

The live demo runs with JavaScript. Its code is below.

React HTML
import { Icon, TextField } from '@kyralabs/m3exp-web/react';

export default function States() {
  return (
    <div style={{ display: 'grid', gap: 'var(--m3e-sys-space-300)', inlineSize: '100%' }}>
      <TextField
        variant="outlined"
        label="Email"
        type="email"
        defaultValue="ada@example"
        isInvalid
        errorMessage="Enter a full address, like ada@example.com"
        trailingIcon={<Icon name="error" />}
      />
      <TextField
        variant="filled"
        label="Amount"
        inputMode="decimal"
        prefix="€"
        suffix="EUR"
        leadingIcon={<Icon name="payments" />}
      />
      <TextField variant="outlined" label="Bio" multiline maxLength={120} />
      <TextField variant="filled" label="Account" defaultValue="ada-lovelace" isDisabled />
    </div>
  );
}
<div style="display: grid; gap: var(--m3e-sys-space-300); inline-size: 100%">
  <m3e-text-field
    variant="outlined"
    label="Email"
    type="email"
    value="ada@example"
    invalid
    error-text="Enter a full address, like ada@example.com"
    trailing-icon="error"
  ></m3e-text-field>
  <m3e-text-field
    variant="filled"
    label="Amount"
    inputmode="decimal"
    prefix-text="€"
    suffix-text="EUR"
    leading-icon="payments"
  ></m3e-text-field>
  <m3e-text-field variant="outlined" label="Bio" multiline maxlength="120"></m3e-text-field>
  <m3e-text-field variant="filled" label="Account" value="ada-lovelace" disabled></m3e-text-field>
</div>

Run the HTML version (custom elements, no framework).

API

React

<TextField>

Text field (COMPONENT_SPEC §26, Compose TextField / OutlinedTextField): React Aria useTextField on the m3e-css .m3e-text-field contract, the same DOM as <m3e-text-field>. The ref is the native input (or textarea). The label float, colors and the outlined notch are CSS (text-field.css); this adds the live counter and, where CSS field-sizing is missing, the growing textarea.

Prop Type Default Description
variant "filled" | "outlined" Filled (default) or outlined (Compose TextField / OutlinedTextField).
label ReactNode The visible label (a <label for>); without it name the field with aria-label.
labelPosition "above" | "inside" | "cutout" Compose's TextFieldLabelPosition: inside (filled default) and cutout (outlined default) follow the variant; above puts the label over the container, always minimized.
description ReactNode Supporting text under the field (description in React Aria terms).
supportingText ReactNode Alias of description (Compose supportingText).
errorMessage ReactNode Shown instead of the supporting text while invalid, with the "alert" role.
isInvalid boolean Error colors and aria-invalid (Compose isError).
isRequired boolean aria-required and a "*" after the label (m3.material.io).
leadingIcon ReactNode A Material Symbols name (a decorative icon) or a node (e.g. an icon button).
prefix ReactNode Text before / after the input, shown while the label is minimized.
maxLength number Limits the input and shows the character counter ("n/max").
counterLabel string The counter's accessible prefix (default "Character count", m3.material.io).
multiline boolean A <textarea> that grows with its text (Compose TextFieldLineLimits.MultiLine).
isDisabled boolean
isReadOnly boolean
trailingIcon ReactNode
suffix ReactNode
value string
defaultValue string
placeholder string
type 'text' | 'search' | 'url' | 'tel' | 'email' | 'password'
inputMode 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'
name string
autoComplete string
autoFocus boolean
id string
className string
style CSSProperties
aria-label string
aria-labelledby string
aria-describedby string
onChange (value: string) => void
onFocus (e: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void
onBlur (e: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void
onKeyDown (e: React.KeyboardEvent) => void

Custom elements

<m3e-text-field>

import '@kyralabs/m3e-elements/text-field.js';

<m3e-text-field label="Email" variant="outlined"> (COMPONENT_SPEC §26): renders the m3e-css text field inside itself (the host is display: contents) around a native <input> (or, with multiline, a <textarea>) that takes part in forms and fires the native input / change events. The label float, colors and outlined notch are CSS; the element adds what CSS cannot:

  • the live character counter (maxlength: "n/max", read as "Character count, n/max");
  • a growing textarea where CSS field-sizing: content is missing (Compose's multi-line field grows with its lines);
  • the value property kept in step with what the user types.

Attributes: variant (filled, the default, or outlined), label, label-position (above; inside / cutout follow the variant), value, placeholder, type, name, autocomplete, inputmode, supporting-text, error-text (shown instead of the supporting text while invalid), invalid (aria-invalid: error colors), disabled, readonly, required (aria-required and the "*"), maxlength (with the counter), multiline, leading-icon, trailing-icon (Material Symbols names), prefix-text, suffix-text, counter-label.

Attribute Property Type Notes
variant variant string
label label string
label-position labelPosition string
value value string
placeholder placeholder string
type type string
name name string
autocomplete autocomplete string
inputmode inputmode string
supporting-text supportingText string
error-text errorText string
invalid invalid boolean
disabled disabled boolean
readonly readonly boolean
required required boolean
maxlength maxlength number
multiline multiline boolean
leading-icon leadingIcon string
trailing-icon trailingIcon string
prefix-text prefixText string
suffix-text suffixText string
counter-label counterLabel string

CSS

text-field.css

@import '@kyralabs/m3e-css/text-field.css';

Text fields (COMPONENT_SPEC §26): filled (default) and outlined, single-line <input> or multi-line <textarea>, label, placeholder, leading / trailing icons, prefix / suffix, supporting text, character counter, error, disabled, read-only. No JavaScript needed: the label floats from CSS alone (below); <m3e-text-field> / m3e-react only add the live counter and the textarea growth where field-sizing is missing.

<div class="m3e-text-field m3e-text-field--outlined">
  <div class="m3e-text-field__container">
    <span class="m3e-text-field__leading" aria-hidden="true"><span class="m3e-icon">search</span></span>
    <label class="m3e-text-field__label" for="email">Email</label>
    <span class="m3e-text-field__prefix" id="email-prefix">@</span>
    <input class="m3e-text-field__input" id="email" placeholder=" " maxlength="20"
           aria-describedby="email-supporting email-counter" />
    <span class="m3e-text-field__suffix">.com</span>
    <span class="m3e-text-field__trailing"><button class="m3e-icon-button …" aria-label="Clear">…</button></span>
    <fieldset class="m3e-text-field__outline" aria-hidden="true">          (outlined only)
      <legend class="m3e-text-field__notch"><span>Email</span></legend>   (the label text again)
    </fieldset>
  </div>
  <div class="m3e-text-field__supporting">
    <span class="m3e-text-field__supporting-text" id="email-supporting">Work address</span>
    <span class="m3e-text-field__counter" id="email-counter">0/20</span>
  </div>
</div>

Every part but the container and the input is optional. Modifiers: --outlined, --label-above (TextFieldLabelPosition.Above, owner decision M8 #25), --multiline (a <textarea>). States come from the input: :focus, :disabled, aria-invalid="true" (error colors), :required / aria-required="true" (a "*" after the label), placeholder text (below).

The zero-JavaScript label float: the label is minimized while the input is focused, not empty or autofilled, :has(.m3e-text-field__input:is(:focus, :not(:placeholder-shown), :autofill)). :placeholder-shown only matches an input with a placeholder, so give one (placeholder=" " when no placeholder text is wanted); without any the label simply stays minimized. The label progress is a registered number (--_m3e-text-field-progress) transitioned with the fast spatial spring's linear() easing, and the label's font-size / line-height / letter-spacing, position and the outlined notch are calc()s of it: Compose lerps the label TextStyle (bodyLarge → bodySmall) and its placement by one labelProgress (owner decision M8 #25). font-optical-sizing stays auto: the glyphs follow the animated size each frame like a static label of that size in Chromium, Firefox and WebKit (checked; WebKit's auto opsz differs from the px size everywhere, not only here).

The outlined notch: a fieldset draws the outline and its legend cuts it, so it follows the writing direction and forced colors with no script. The legend holds the label text again (aria-hidden, invisible) at the label's lerped size × progress, padded by the cutout padding while the progress is positive: Compose's outlineCutout (label width × progress + 2 × 4dp from the start padding − 4dp, nothing at progress ≤ 0).

164 custom properties it reads
Custom property Default
--m3e-comp-filled-text-field-container-shape-end-end var(--m3e-sys-shape-corner-extra-small-top-end-end)
--m3e-comp-filled-text-field-container-shape-end-start var(--m3e-sys-shape-corner-extra-small-top-end-start)
--m3e-comp-filled-text-field-container-shape-start-end var(--m3e-sys-shape-corner-extra-small-top-start-end)
--m3e-comp-filled-text-field-container-shape-start-start var(--m3e-sys-shape-corner-extra-small-top-start-start)
--m3e-comp-filled-text-field-input-font var(--m3e-sys-typescale-body-large-font)
--m3e-comp-filled-text-field-input-line-height var(--m3e-sys-typescale-body-large-line-height)
--m3e-comp-filled-text-field-input-size var(--m3e-sys-typescale-body-large-size)
--m3e-comp-filled-text-field-input-tracking var(--m3e-sys-typescale-body-large-tracking)
--m3e-comp-filled-text-field-input-weight var(--m3e-sys-typescale-body-large-weight)
--m3e-comp-filled-text-field-leading-icon-size 24px
--m3e-comp-filled-text-field-trailing-icon-size 24px
--m3e-comp-minimum-interactive-component-size 48px
--m3e-comp-outlined-text-field-container-shape var(--m3e-sys-shape-corner-extra-small)
--m3e-comp-outlined-text-field-input-font var(--m3e-sys-typescale-body-large-font)
--m3e-comp-outlined-text-field-input-line-height var(--m3e-sys-typescale-body-large-line-height)
--m3e-comp-outlined-text-field-input-size var(--m3e-sys-typescale-body-large-size)
--m3e-comp-outlined-text-field-input-tracking var(--m3e-sys-typescale-body-large-tracking)
--m3e-comp-outlined-text-field-input-weight var(--m3e-sys-typescale-body-large-weight)
--m3e-comp-outlined-text-field-leading-icon-size 24px
--m3e-comp-outlined-text-field-trailing-icon-size 24px
--m3e-comp-text-field-above-label-bottom-padding 4px
--m3e-comp-text-field-above-label-horizontal-padding 4px
--m3e-comp-text-field-affix-alpha-duration var(--m3e-sys-motion-spring-fast-effects-duration)
--m3e-comp-text-field-affix-alpha-easing var(--m3e-sys-motion-spring-fast-effects-easing)
--m3e-comp-text-field-affix-font var(--m3e-sys-typescale-body-large-font)
--m3e-comp-text-field-affix-line-height var(--m3e-sys-typescale-body-large-line-height)
--m3e-comp-text-field-affix-padding 2px
--m3e-comp-text-field-affix-size var(--m3e-sys-typescale-body-large-size)
--m3e-comp-text-field-affix-tracking var(--m3e-sys-typescale-body-large-tracking)
--m3e-comp-text-field-affix-weight var(--m3e-sys-typescale-body-large-weight)
--m3e-comp-text-field-container-color-duration var(--m3e-sys-motion-spring-fast-effects-duration)
--m3e-comp-text-field-container-color-easing var(--m3e-sys-motion-spring-fast-effects-easing)
--m3e-comp-text-field-counter-space 16px
--m3e-comp-text-field-cutout-label-space calc(var(--m3e-sys-typescale-body-small-line-height) / 2)
--m3e-comp-text-field-cutout-padding 4px
--m3e-comp-text-field-filled-container-color var(--m3e-sys-color-surface-container-highest)
--m3e-comp-text-field-filled-container-color-disabled var(--m3e-sys-color-surface-container-highest)
--m3e-comp-text-field-filled-container-color-error var(--m3e-sys-color-surface-container-highest)
--m3e-comp-text-field-filled-container-color-focused var(--m3e-sys-color-surface-container-highest)
--m3e-comp-text-field-filled-cursor-color var(--m3e-sys-color-primary)
--m3e-comp-text-field-filled-cursor-color-error var(--m3e-sys-color-error)
--m3e-comp-text-field-filled-indicator-color var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-filled-indicator-color-disabled color-mix(in srgb, var(--m3e-sys-color-on-surface) calc(0.38 * 100%), transparent)
--m3e-comp-text-field-filled-indicator-color-error var(--m3e-sys-color-error)
--m3e-comp-text-field-filled-indicator-color-focused var(--m3e-sys-color-primary)
--m3e-comp-text-field-filled-label-color var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-filled-label-color-disabled color-mix(in srgb, var(--m3e-sys-color-on-surface) calc(0.38 * 100%), transparent)
--m3e-comp-text-field-filled-label-color-error var(--m3e-sys-color-error)
--m3e-comp-text-field-filled-label-color-focused var(--m3e-sys-color-primary)
--m3e-comp-text-field-filled-leading-icon-color var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-filled-leading-icon-color-disabled color-mix(in srgb, var(--m3e-sys-color-on-surface) calc(0.38 * 100%), transparent)
--m3e-comp-text-field-filled-leading-icon-color-error var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-filled-leading-icon-color-focused var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-filled-placeholder-color var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-filled-placeholder-color-disabled color-mix(in srgb, var(--m3e-sys-color-on-surface) calc(0.38 * 100%), transparent)
--m3e-comp-text-field-filled-placeholder-color-error var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-filled-placeholder-color-focused var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-filled-prefix-color var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-filled-prefix-color-disabled color-mix(in srgb, var(--m3e-sys-color-on-surface-variant) calc(0.38 * 100%), transparent)
--m3e-comp-text-field-filled-prefix-color-error var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-filled-prefix-color-focused var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-filled-suffix-color var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-filled-suffix-color-disabled color-mix(in srgb, var(--m3e-sys-color-on-surface-variant) calc(0.38 * 100%), transparent)
--m3e-comp-text-field-filled-suffix-color-error var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-filled-suffix-color-focused var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-filled-supporting-color var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-filled-supporting-color-disabled color-mix(in srgb, var(--m3e-sys-color-on-surface) calc(0.38 * 100%), transparent)
--m3e-comp-text-field-filled-supporting-color-error var(--m3e-sys-color-error)
--m3e-comp-text-field-filled-supporting-color-focused var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-filled-text-color var(--m3e-sys-color-on-surface)
--m3e-comp-text-field-filled-text-color-disabled color-mix(in srgb, var(--m3e-sys-color-on-surface) calc(0.38 * 100%), transparent)
--m3e-comp-text-field-filled-text-color-error var(--m3e-sys-color-on-surface)
--m3e-comp-text-field-filled-text-color-focused var(--m3e-sys-color-on-surface)
--m3e-comp-text-field-filled-trailing-icon-color var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-filled-trailing-icon-color-disabled color-mix(in srgb, var(--m3e-sys-color-on-surface) calc(0.38 * 100%), transparent)
--m3e-comp-text-field-filled-trailing-icon-color-error var(--m3e-sys-color-error)
--m3e-comp-text-field-filled-trailing-icon-color-focused var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-icon-padding calc((48px - 24px) / 2)
--m3e-comp-text-field-indicator-color-duration var(--m3e-sys-motion-spring-fast-effects-duration)
--m3e-comp-text-field-indicator-color-easing var(--m3e-sys-motion-spring-fast-effects-easing)
--m3e-comp-text-field-indicator-thickness 1px
--m3e-comp-text-field-indicator-thickness-focused 2px
--m3e-comp-text-field-indicator-width-duration var(--m3e-sys-motion-spring-fast-spatial-duration)
--m3e-comp-text-field-indicator-width-easing var(--m3e-sys-motion-spring-fast-spatial-easing)
--m3e-comp-text-field-label-color-duration var(--m3e-sys-motion-spring-fast-effects-duration)
--m3e-comp-text-field-label-color-easing var(--m3e-sys-motion-spring-fast-effects-easing)
--m3e-comp-text-field-label-duration var(--m3e-sys-motion-spring-fast-spatial-duration)
--m3e-comp-text-field-label-easing var(--m3e-sys-motion-spring-fast-spatial-easing)
--m3e-comp-text-field-label-expanded-font var(--m3e-sys-typescale-body-large-font)
--m3e-comp-text-field-label-expanded-line-height var(--m3e-sys-typescale-body-large-line-height)
--m3e-comp-text-field-label-expanded-size var(--m3e-sys-typescale-body-large-size)
--m3e-comp-text-field-label-expanded-tracking var(--m3e-sys-typescale-body-large-tracking)
--m3e-comp-text-field-label-expanded-weight var(--m3e-sys-typescale-body-large-weight)
--m3e-comp-text-field-label-minimized-line-height var(--m3e-sys-typescale-body-small-line-height)
--m3e-comp-text-field-label-minimized-size var(--m3e-sys-typescale-body-small-size)
--m3e-comp-text-field-label-minimized-tracking var(--m3e-sys-typescale-body-small-tracking)
--m3e-comp-text-field-label-vertical-padding 8px
--m3e-comp-text-field-min-height 56px
--m3e-comp-text-field-min-supporting-line-height 16px
--m3e-comp-text-field-min-text-line-height 24px
--m3e-comp-text-field-min-width 280px
--m3e-comp-text-field-outline-thickness 1px
--m3e-comp-text-field-outline-thickness-focused 2px
--m3e-comp-text-field-outlined-container-color transparent
--m3e-comp-text-field-outlined-container-color-disabled transparent
--m3e-comp-text-field-outlined-container-color-error transparent
--m3e-comp-text-field-outlined-container-color-focused transparent
--m3e-comp-text-field-outlined-cursor-color var(--m3e-sys-color-primary)
--m3e-comp-text-field-outlined-cursor-color-error var(--m3e-sys-color-error)
--m3e-comp-text-field-outlined-indicator-color var(--m3e-sys-color-outline)
--m3e-comp-text-field-outlined-indicator-color-disabled color-mix(in srgb, var(--m3e-sys-color-on-surface) calc(0.12 * 100%), transparent)
--m3e-comp-text-field-outlined-indicator-color-error var(--m3e-sys-color-error)
--m3e-comp-text-field-outlined-indicator-color-focused var(--m3e-sys-color-primary)
--m3e-comp-text-field-outlined-label-color var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-outlined-label-color-disabled color-mix(in srgb, var(--m3e-sys-color-on-surface) calc(0.38 * 100%), transparent)
--m3e-comp-text-field-outlined-label-color-error var(--m3e-sys-color-error)
--m3e-comp-text-field-outlined-label-color-focused var(--m3e-sys-color-primary)
--m3e-comp-text-field-outlined-leading-icon-color var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-outlined-leading-icon-color-disabled color-mix(in srgb, var(--m3e-sys-color-on-surface) calc(0.38 * 100%), transparent)
--m3e-comp-text-field-outlined-leading-icon-color-error var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-outlined-leading-icon-color-focused var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-outlined-min-height 56px
--m3e-comp-text-field-outlined-min-width 280px
--m3e-comp-text-field-outlined-placeholder-color var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-outlined-placeholder-color-disabled color-mix(in srgb, var(--m3e-sys-color-on-surface) calc(0.38 * 100%), transparent)
--m3e-comp-text-field-outlined-placeholder-color-error var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-outlined-placeholder-color-focused var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-outlined-prefix-color var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-outlined-prefix-color-disabled color-mix(in srgb, var(--m3e-sys-color-on-surface-variant) calc(0.38 * 100%), transparent)
--m3e-comp-text-field-outlined-prefix-color-error var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-outlined-prefix-color-focused var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-outlined-suffix-color var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-outlined-suffix-color-disabled color-mix(in srgb, var(--m3e-sys-color-on-surface-variant) calc(0.38 * 100%), transparent)
--m3e-comp-text-field-outlined-suffix-color-error var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-outlined-suffix-color-focused var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-outlined-supporting-color var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-outlined-supporting-color-disabled color-mix(in srgb, var(--m3e-sys-color-on-surface) calc(0.38 * 100%), transparent)
--m3e-comp-text-field-outlined-supporting-color-error var(--m3e-sys-color-error)
--m3e-comp-text-field-outlined-supporting-color-focused var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-outlined-text-color var(--m3e-sys-color-on-surface)
--m3e-comp-text-field-outlined-text-color-disabled color-mix(in srgb, var(--m3e-sys-color-on-surface) calc(0.38 * 100%), transparent)
--m3e-comp-text-field-outlined-text-color-error var(--m3e-sys-color-on-surface)
--m3e-comp-text-field-outlined-text-color-focused var(--m3e-sys-color-on-surface)
--m3e-comp-text-field-outlined-trailing-icon-color var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-outlined-trailing-icon-color-disabled color-mix(in srgb, var(--m3e-sys-color-on-surface) calc(0.38 * 100%), transparent)
--m3e-comp-text-field-outlined-trailing-icon-color-error var(--m3e-sys-color-error)
--m3e-comp-text-field-outlined-trailing-icon-color-focused var(--m3e-sys-color-on-surface-variant)
--m3e-comp-text-field-padding 16px
--m3e-comp-text-field-placeholder-font var(--m3e-sys-typescale-body-large-font)
--m3e-comp-text-field-placeholder-in-duration var(--m3e-sys-motion-spring-slow-effects-duration)
--m3e-comp-text-field-placeholder-in-easing var(--m3e-sys-motion-spring-slow-effects-easing)
--m3e-comp-text-field-placeholder-line-height var(--m3e-sys-typescale-body-large-line-height)
--m3e-comp-text-field-placeholder-out-duration var(--m3e-sys-motion-spring-fast-effects-duration)
--m3e-comp-text-field-placeholder-out-easing var(--m3e-sys-motion-spring-fast-effects-easing)
--m3e-comp-text-field-placeholder-size var(--m3e-sys-typescale-body-large-size)
--m3e-comp-text-field-placeholder-tracking var(--m3e-sys-typescale-body-large-tracking)
--m3e-comp-text-field-placeholder-weight var(--m3e-sys-typescale-body-large-weight)
--m3e-comp-text-field-selection-color color-mix(in srgb, var(--m3e-sys-color-primary) calc(0.4 * 100%), transparent)
--m3e-comp-text-field-supporting-font var(--m3e-sys-typescale-body-small-font)
--m3e-comp-text-field-supporting-line-height var(--m3e-sys-typescale-body-small-line-height)
--m3e-comp-text-field-supporting-size var(--m3e-sys-typescale-body-small-size)
--m3e-comp-text-field-supporting-top-padding 4px
--m3e-comp-text-field-supporting-tracking var(--m3e-sys-typescale-body-small-tracking)
--m3e-comp-text-field-supporting-weight var(--m3e-sys-typescale-body-small-weight)

Accessibility

native <input> / <textarea> with a <label for> (React Aria also sets aria-labelledby), aria-describedby → the supporting (or error) text and the counter, aria-invalid="true" for errors (the web's form of Compose's error() semantics; the platform announces the invalid state, so Compose's default "Invalid input" string is not injected), aria-required="true" (React Aria's default validation behavior) with the "*" in the label's accessible name (m3.material.io). While invalid the error text replaces the supporting text (Compose's TextFieldWithErrorState) and has the "alert" role (m3.material.io). Read-only fields stay focusable. Focus shows as the 2dp primary indicator / outline and primary label (Compose); trailing icon buttons keep their own focus ring.

Every interactive part has a target of at least 48 × 48 px, a visible focus ring for keyboard focus only, and works under forced-colors and prefers-reduced-motion.

Sources

Values come from androidx Compose Material3 1.5.0-beta01 at 19991f2c and from m3.material.io, as cited below from COMPONENT_SPEC §26 and the m3e-css sources.

FilledTextFieldTokens.kt / OutlinedTextFieldTokens.kt@19991f2c plus the TextField.kt / OutlinedTextField.kt / TextFieldDefaults / OutlinedTextFieldDefaults / internal/TextFieldImpl.kt values Compose renders (components/text-field-defaults.css; SOURCES register #43). Container: min 280 × 56dp (MinWidth × MinHeight; FilledTextFieldTokens has no height), filled ContainerShape (extra small, top corners) with a bottom indicator line 1dp, 2dp while focused and enabled (Unfocused/FocusedIndicatorThickness, drawn over the content, clipped to the shape; the token DB's 3dp focus thickness is not rendered), outlined extra small with a 1dp / 2dp outline drawn inside the bounds. Paddings (contentPaddingWithLabel only for an Inside label): filled with a label 16dp start / end and TextFieldWithLabelVerticalPadding 8dp top / bottom, the input under the 16dp minimized label line; otherwise 16dp all round (TextFieldPadding). Label (DecoratedLabel): lerp(bodyLarge, bodySmall, progress); filled from the vertical centre (top padding + half the minimized line height, 16dp) to the 8dp top padding, at the text start; outlined from 16dp (after the leading icon) to centred on the top outline (−height / 2) at the 16dp start padding, with half the minimized line height (8dp, topPaddingForLabelCutout) of space above the field and a notch of label width × progress + 2 × 4dp (OutlinedTextFieldInnerPadding) from 12dp, none at progress ≤ 0 (outlineCutout). Icons (24dp) in 48dp boxes, the text 16 − 12 = 4dp after them (textFieldHorizontalIconPadding). Prefix / suffix 2dp from the text (PrefixSuffixTextPadding), body large, shown while the label is minimized; placeholder body large, only while the label is minimized or absent. Supporting text body small, 4dp below (SupportingTopPadding), 16dp in, min 16dp line. Label Above (owner decision M8 #25): TextFieldLabelPosition.Above, always minimized, 4dp in and 4dp above the container, with the no-label paddings. Colors (defaultTextFieldColors / defaultOutlinedTextFieldColors; disabled, then error, then focused, then unfocused): focused indicator / outline, label and caret primary; error (aria-invalid="true") indicator, label, supporting text, trailing icon and caret error, text on-surface, leading icon on-surface-variant (Compose uses the Error*, not the ErrorFocus* tokens); disabled translucent copies (on-surface 0.38, outlined outline on-surface 0.12), and the filled disabled container keeps ContainerColor (not the token file's on-surface 4 %). Hover changes nothing (TextFieldColors has no hover; owner decision M8 #3) and there is no state layer (Compose draws none). Selection: primary at 0.4 (rememberTextSelectionColors). Not in Compose, from m3.material.io (owner rule): the character counter ("n/max", read as "Character count, n/max") at the end of the supporting line, 16dp after the supporting text; the required "*" after the label. The expressive tonal / rounded variant (tonalColors(), roundedShape) is out of scope (owner decision M8 #24).

text-field.css sources

(compose/material3 @ 19991f2c; values in text-field-defaults.css, lines in PARITY.md):

  • TextField.kt / OutlinedTextField.kt: Inside (filled) and Cutout (outlined) label by default, MinWidth × MinHeight (280 × 56dp), the indicator line (bottom, 1dp → 2dp while focused and enabled, color FastEffects / width FastSpatial, snapping while disabled; drawn over the content, clipped to the shape), the Cutout space above an outlined field (bodySmall line height / 2) and the cutout itself (OutlinedTextFieldInnerPadding 4dp).
  • TextFieldImpl.kt: InputPhase (focused / unfocused empty / not empty); label progress FastSpatial; label TextStyle lerp(bodyLarge, bodySmall); filled label from the vertical centre (top padding + half the minimized line height) to the 8dp top padding, the text below it; outlined label from the 16dp padding to −height / 2 (centred on the outline) and from after the leading icon to the 16dp start padding; icons in 48dp boxes, the text 16 − 12 = 4dp after them; prefix / suffix 2dp from the text, shown while the label is minimized (FastEffects); placeholder only while the label is minimized or absent (SlowEffects in, FastEffects out); supporting text 4dp below, 16dp in, bodySmall; Above: the label 4dp in and 4dp above the container, always minimized, with the no-label paddings (16dp).
  • TextFieldDefaults.kt: contentPaddingWithLabel (16 / 8dp) only for an Inside label, else 16dp; defaultTextFieldColors / defaultOutlinedTextFieldColors: disabled, then error, then focused, then unfocused. No hover colors (TextFieldColors has none; owner decision M8 #3) and no state layer (Compose draws none on a text field). The filled disabled container keeps ContainerColor.
  • MaterialTheme.kt: the selection highlight (primary at 0.4).
  • Not Compose (m3.material.io Text fields, per the owner's rule): the character counter at the end of the supporting line, 16dp after the supporting text; the required "*".