Skip to content

ExpressiveCSS

Browse documentation

Text Inputs

Material Design 3 text fields, from the HTML.

View as Markdown

Wrap the control in a .field. The <label> after the control is the floating label. Mark an icon prefix or suffix to set its side, and make it aria-hidden. A <small> holds supporting text. The default is the M3 filled field. Add outlined for the outlined variant.

Tokens follow the M3 text field spec. Height is 56dp. Filled is a surface-container-highest well with 4dp top corners and a 1dp / 2dp bottom indicator. Outlined is a 1dp / 2dp outline at 4dp. The label is body-large at rest and body-small floated. Input text is body-large / on-surface. Icons are 24dp, 12dp from the edge.

Add placeholder=" " (one space) so the label floats with CSS only. A missing placeholder, or any other string, keeps the label floated. Importing the bundle runs Forms.Init(). It validates .validate fields on change and starts textareas and file paths. Character Counter is not in AutoInit().

Supporting text
Invalid characters. Please use 0-9 only.
<div class="field">
  <input id="first_name" type="text" placeholder=" " aria-describedby="first_name_help">
  <label for="first_name">First name</label>
  <small id="first_name_help">Supporting text</small>
</div>

<div class="field outlined">
  <input id="last_name" type="text" placeholder=" ">
  <label for="last_name">Last name</label>
</div>

<div class="field">
  <span class="material-symbols prefix" aria-hidden="true">place</span>
  <span class="material-symbols suffix" aria-hidden="true">gps_fixed</span>
  <input id="loc" type="text" placeholder=" ">
  <label for="loc">Location</label>
</div>

Put invalid or aria-invalid="true" on the input for the error state. Do not put error on the wrapper. That class is a color utility and fills the field. .supporting-text and .prefix / .suffix remain as aliases.

Input types

email, password, and the other native text-like types get the same styling. Add validate to check the value with HTML5 constraint validation on change. It toggles invalid and aria-invalid="true" on the input, so a screen reader hears the error state too.

It also adds the field's [data-error] supporting text to the input's aria-describedby, so the error the CSS shows is read with the field. It gives that text an id if it has none and keeps the ids already there. There is no green valid style.

Supporting text
<div class="field">
  <input id="email" type="email" class="validate" placeholder=" " aria-describedby="email_help">
  <label for="email">Email</label>
  <small id="email_help" data-error="Enter a valid email">Supporting text</small>
</div>

validate also honors data-length and marks the field invalid when the value is longer than that number. Prefer maxlength when you want the browser to cap input.

Inline

Add inline to sit the field in a line of text.

This is an inline input field:

This is an inline input field:
<span class="field inline">
  <input id="email_inline" type="email" class="validate" placeholder=" ">
  <label for="email_inline">Email</label>
</span>

Password visibility

A trailing .icon-button with suffix is a control in the field. It keeps its 40dp target and takes clicks. Give it password-toggle, two icons and aria-pressed="false", and pressing it shows the password as text. The name stays "Show password", and aria-pressed says which way it is. Submitting or resetting the form hides the password again.

<div class="field">
  <input id="password" type="password" placeholder=" " autocomplete="current-password">
  <label for="password">Password</label>
  <button type="button" class="icon-button suffix password-toggle"
          aria-label="Show password" aria-pressed="false">
    <span class="material-symbols" aria-hidden="true">visibility</span>
    <span class="material-symbols" aria-hidden="true">visibility_off</span>
  </button>
</div>

Number stepper

Add two trailing icon buttons with data-step="down" and data-step="up" to step a number input the way the arrow keys do, within its min, max and step. Name each button with what it changes. The Forms behavior does the stepping, and each press fires input and change. The browser's own spin buttons are hidden. A disabled, read-only or step="any" input is left alone.

<div class="field">
  <input id="quantity" type="number" placeholder=" " value="1" min="1" max="10">
  <label for="quantity">Quantity</label>
  <button type="button" class="icon-button suffix" data-step="down" aria-label="Decrease quantity">
    <span class="material-symbols" aria-hidden="true">remove</span>
  </button>
  <button type="button" class="icon-button suffix" data-step="up" aria-label="Increase quantity">
    <span class="material-symbols" aria-hidden="true">add</span>
  </button>
</div>

One-time code

input.otp draws one native input as a row of cells. Paste, autofill from autocomplete="one-time-code" and undo keep working, and a screen reader hears one field. Give it inputmode="numeric" and a maxlength equal to --md-comp-otp-length (6 by default). Label it outside any .field.

<label for="code">Verification code</label>
<input id="code" class="otp" type="text" inputmode="numeric"
       autocomplete="one-time-code" maxlength="6" pattern="[0-9]{6}">

Textarea

Use textarea.expressive-textarea inside .field. Textareas grow with their content. Forms.Init() starts every .expressive-textarea on DOMContentLoaded.

<div class="field">
  <textarea id="textarea1" class="expressive-textarea" placeholder=" "></textarea>
  <label for="textarea1">Textarea</label>
</div>

If you add a textarea after load, initialize it yourself:

Expressive.Forms.InitTextarea(document.querySelector('#textarea1'));

Setting .value in script does not resize the field. Call textareaAutoResize afterwards.

File input

A .file-field.field pairs a button with a path field. Forms.Init() copies the chosen file name into input.file-path.

<div class="file-field field">
  <label class="button">
    File
    <input type="file">
  </label>
  <div class="file-path-wrapper">
    <input class="file-path" type="text" placeholder=" " readonly aria-label="Selected file">
  </div>
</div>

Add multiple to allow more than one file.

Color

An <input type="color"> keeps the browser's picker and shows as a 40px round swatch of the chosen color in a 48px target. Label it like any field, and keep it outside a .field, which would give it the 56dp text well.

<label for="accent">Accent color</label>
<input id="accent" type="color" value="#c6a0f6">

Character counter

Character Counter is not in AutoInit(). It reads maxlength (not data-length) and writes current/max into a .character-counter span. Overflow adds invalid on the field.

Expressive.CharacterCounter.init(
  document.querySelectorAll('#input_text_counter, #textarea_counter')
);

No matching pages

  • Source color

    The seed every generated ramp derives from. Pick one and browse the docs — the whole theme follows. Error does not: it is a fixed hue.

    Default source: Mist Iris.

    ExpressiveCSS presets: Lagoon, Harbor, Breeze, Cobalt, Iris.

    Bold

    Soft

    Mist

    Glow