Form adorn

Decorate inputs with icons, text, and more using a custom wrapper that easily handles styling and positioning.

Layer: forms

How it works

Apply both .form-control and .form-adorn to a wrapper element. .form-control provides the border, background, padding, and focus states; .form-adorn uses flexbox to position adornments alongside a "ghost input," a form control that has virtually no visual styling. The .form-ghost input inside is transparent and inherits styles from the wrapper.

Use a <label> for the wrapper. The wrapper looks like an input, so people click the icon, the text, or the padding around them and expect the field to take focus. A <label> gives you that for free, because clicking a label focuses the control it labels. No JavaScript is involved.

Mark the adornments with aria-hidden="true". A wrapping <label> labels the input, so without it an adornment such as $ joins the accessible name and a screen reader announces "Amount $" instead of "Amount". An adornment is decorative, so keep it out of the accessibility tree and put anything meaningful in the visible label or in a .form-text description.

An element can have more than one label, so the wrapping <label> and a separate <label for="…"> work together. Do not put a button or another interactive control inside the wrapper — a <label> must not contain interactive content.

See the form control documentation for more information on the .form-control and .form-ghost classes.

Example

Wrap an icon and a .form-ghost input inside a <label> with .form-control and .form-adorn. Place the adornment before the input in the DOM for start position (left in LTR).

HTML
<label class="form-control form-adorn">
  <div class="form-adorn-icon" aria-hidden="true">
    <svg class="bi" width="16" height="16"><use href="#search" /></svg>
  </div>
  <input type="search" class="form-ghost" placeholder="Search...">
</label>

Use .form-adorn-end to position the adornment on the trailing side (keeps DOM order, uses CSS to flip visually):

HTML
<label class="form-control form-adorn form-adorn-end">
  <div class="form-adorn-icon" aria-hidden="true">
    <svg class="bi" width="16" height="16"><use href="#envelope" /></svg>
  </div>
  <input type="email" class="form-ghost" placeholder="you@example.com">
</label>

With labels

Add a second label outside the .form-adorn wrapper to name the field. Point it at the input with for, so the input keeps its name even if you change the wrapper:

HTML
<div>
  <label for="searchInput" class="form-label">Search</label>
  <label class="form-control form-adorn">
    <div class="form-adorn-icon" aria-hidden="true">
      <svg class="bi" width="16" height="16"><use href="#search" /></svg>
    </div>
    <input type="search" class="form-ghost" id="searchInput" placeholder="Search...">
  </label>
</div>
<div>
  <label for="emailInput" class="form-label">Email address</label>
  <label class="form-control form-adorn form-adorn-end">
    <div class="form-adorn-icon" aria-hidden="true">
      <svg class="bi" width="16" height="16"><use href="#envelope" /></svg>
    </div>
    <input type="email" class="form-ghost" id="emailInput" placeholder="you@example.com">
  </label>
</div>

With form field

Wrap a .form-adorn in a .form-field to pair it with a label and description text.

Search across all pages and posts.
HTML
<div class="form-field">
  <label for="adornField" class="form-label">Search</label>
  <label class="form-control form-adorn">
    <div class="form-adorn-icon" aria-hidden="true">
      <svg class="bi" width="16" height="16"><use href="#search" /></svg>
    </div>
    <input type="search" class="form-ghost" id="adornField" placeholder="Search...">
  </label>
  <small class="form-text">Search across all pages and posts.</small>
</div>

Text adornments

Use .form-adorn-text for currency symbols, units, domain suffixes, and other text-based adornments. Text adornments auto-size to their content.

HTML
<label class="form-control form-adorn">
  <span class="form-adorn-text" aria-hidden="true">$</span>
  <input type="text" class="form-ghost" placeholder="0.00">
</label>
<label class="form-control form-adorn form-adorn-end">
  <span class="form-adorn-text" aria-hidden="true">USD</span>
  <input type="text" class="form-ghost" placeholder="Amount">
</label>
<label class="form-control form-adorn">
  <span class="form-adorn-text" aria-hidden="true">https://</span>
  <input type="text" class="form-ghost" placeholder="example.com">
</label>
<label class="form-control form-adorn form-adorn-end">
  <span class="form-adorn-text" aria-hidden="true">@example.com</span>
  <input type="text" class="form-ghost" placeholder="username">
</label>

Sizing

Use .form-adorn-sm or .form-adorn-lg on the wrapper to adjust sizing.

HTML
<label class="form-control form-control-sm form-adorn">
  <div class="form-adorn-icon" aria-hidden="true">
    <svg class="bi" width="16" height="16"><use href="#search" /></svg>
  </div>
  <input type="text" class="form-ghost" placeholder="Small input">
</label>
<label class="form-control form-adorn">
  <div class="form-adorn-icon" aria-hidden="true">
    <svg class="bi" width="16" height="16"><use href="#search" /></svg>
  </div>
  <input type="text" class="form-ghost" placeholder="Default input">
</label>
<label class="form-control form-control-lg form-adorn">
  <div class="form-adorn-icon" aria-hidden="true">
    <svg class="bi" width="16" height="16"><use href="#search" /></svg>
  </div>
  <input type="text" class="form-ghost" placeholder="Large input">
</label>

CSS

Variables

// stylelint-disable-next-line scss/dollar-variable-default
$form-adorn-tokens: defaults(
  (
    --form-adorn-gap: .375rem,
    --form-adorn-icon-size: 1rem,
    --form-adorn-icon-color: var(--fg-2),
  ),
  $form-adorn-tokens
);