Skip to content

Input Group

One field with inset prefix or suffix text, icons, and trailing actions (reveal, clear, copy). The group owns the border and radius — addons are not separate boxes.

Wrap the group in a Field for the label. For a submit beside the field, put a sibling button in af-inline — do not join a filled button onto the input.

$ USD
@
https://
<div class="af-field">
  <label class="af-field__label" for="ig-amount">Amount</label>
  <div class="af-input-group">
    <span class="af-input-group-addon">$</span>
    <input type="text" id="ig-amount" class="af-input" inputmode="decimal" placeholder="0.00" />
    <span class="af-input-group-addon">USD</span>
  </div>
</div>

Decorative icons go in af-input-group-addon with aria-hidden="true". The field label still names the control.

<div class="af-field">
  <label class="af-field__label" for="ig-search">Search</label>
  <div class="af-input-group">
    <span class="af-input-group-addon" aria-hidden="true">
      <svg viewBox="0 0 24 24" fill="currentColor">
        <path d="M15.5 14h-.79l-.28-.27A6.47 6.47 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" />
      </svg>
    </span>
    <input type="search" id="ig-search" class="af-input" placeholder="Find packages" />
  </div>
</div>

af-input-group-action is a compact in-field control — reveal, clear, copy. Use type="button" so it does not submit. Icon-only actions need aria-label. Toggle password visibility and clear in your own JavaScript; this pattern is CSS-only.

<div class="af-field">
  <label class="af-field__label" for="ig-password">Password</label>
  <div class="af-input-group">
    <input type="password" id="ig-password" class="af-input" placeholder="••••••••" autocomplete="current-password" />
    <button type="button" class="af-input-group-action" aria-label="Show password" aria-pressed="false">
      <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
        <path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z" />
      </svg>
    </button>
  </div>
</div>

Short text actions work the same way:

<div class="af-field">
  <label class="af-field__label" for="ig-copy">Share link</label>
  <div class="af-input-group">
    <input type="text" id="ig-copy" class="af-input" value="https://www.airframeui.com" readonly />
    <button type="button" class="af-input-group-action">Copy</button>
  </div>
</div>

Reveal and clear are your JavaScript. Toggle type and keep aria-label / aria-pressed in sync:

button.addEventListener('click', () => {
  const show = input.type === 'password';
  input.type = show ? 'text' : 'password';
  button.setAttribute('aria-pressed', String(show));
  button.setAttribute('aria-label', show ? 'Hide password' : 'Show password');
});

A primary action next to the field is a sibling, not an addon. Add af-grow so the group fills the row instead of staying width: 100%.

<div class="af-inline">
  <div class="af-input-group af-grow">
    <input type="search" class="af-input" placeholder="Find packages" aria-label="Search" />
  </div>
  <button type="submit" class="af-btn">Search</button>
</div>