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().
<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.
<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:
<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')
);