Design system Actions and links

Icon button

Icon-only action for toolbars, close marks and compact disclosure controls. A short tooltip names the action; the accessible name can also identify its target.

.mk-icon-btn.mk-add-stay

Overview

An icon button is an action whose face is a single glyph: a toolbar act, a close mark, a compact disclosure. The glyph replaces permanent text on screen; a short tooltip makes the action visible on intent, while the accessible name can also identify the specific target.

Edit Copy Close Expand

Usage

  • Place a familiar action in limited spaceUse an icon button for recognised actions such as close, edit, copy, overflow, or compact disclosure when a text label would crowd the surface.
  • Show a short tooltip and provide an accessible nameHover and keyboard focus reveal a concise action such as Edit or Close. The accessible name may add the target when repeated controls must be distinguished.
  • Keep the complete target intactThe visible icon may be compact, but the button retains the required interaction target and the standard hover, keyboard-focus, disabled, and destructive treatments.

Anatomy

1
2
  1. 1Face. A 32px square, so a toolbar stays dense — shown here in its hover dress, because at rest the seat is transparent and there would be nothing to point at. An invisible expander carries the hit area to 44px: the visible box and the target are different things.
  2. 2Glyph. 16px from the registry, drawn in the action ink — a control is not a caption. Held to 3:1 non-text contrast, the same legibility rule text answers at its own floor.

Variants and states

The size ladder

SizeSpecimenWhere
S.mk-icon-btn-s 28px — inside a dense row. The hit area still expands to 44px.
M.mk-icon-btn default 32px — toolbars and card corners.
L.mk-icon-btn-l 40px — a prominent act beside larger type.
XL.mk-icon-btn-xl 48px — the whole target is visible, for mobile and high-emphasis seats.

The disclosure exception

A disclosure is a quiet structural marker, not an act, so it breaks two of the rules above on purpose: its triangle is 20px filled rather than 16px stroked, and it rests in the muted ink whether it sits at the left of a widget head or the right of a row. Its state is aria-expanded, and the chevron mirrors it.

The destructive face

Delete wears its consequence. A remove control rests in the error ink and answers hover with the error tint, the same grammar as the destructive button tier, so a glance separates the tool that edits a row from the one that destroys it.

States

StateSpecimenWhat changes
Rest Transparent seat, glyph in the action ink.
Hover The sunken surface fills the seat and the border takes the brand accent.
Keyboard focus The standard 2px camel outline at 2px offset — tab into the row to see it.
Disabled Dimmed glyph, dead hover, cursor: not-allowed. The reason lives beside the control, as every blocked action's does.

Timeline add

The terminal action on a timeline: after the final entry, a circular node sits on the connector with its label beneath. It is its own geometry rather than a decorated ghost button — the 24px node has to mask the connector line it sits on, and the registered 16px plus stays mathematically centred across every Builder state. One form, no tiers.

States

StateSpecimenWhat changes
Rest Accent ink, opaque node on the connector.
Hover The label underlines and the node fills with the accent wash. Ring and plus hold still, so the connector under the node never shows through.
Keyboard focus The standard 2px camel outline around the whole control.
Blocked No fill, a hairline ring, muted ink. aria-disabled="true" keeps the control reachable and its reason readable — the itinerary's end may be blocked by an unset return date, and the agent deserves to learn that.
  • The label is the nameVisible words inside the button — unlike the icon button above, nothing rides on aria-label and nothing can fall out of sync. The plus is decoration: aria-hidden="true".
  • The floor holdsThe target is 44px in both axes while the visible node stays 24px; the route's own behaviour decides where the terminal node lands.
  • Adding inside the dayThe day's own service button does that — this control owns a point on the route line, nothing inside the day.
  • An inline add beside a titleUse the boxless quiet button. This control exists only where a connector must be masked.

Do and don't

Do

The accessible name says the object: "Remove the Hajar stay". Five identical trash cans with five different objects need five different names.

Don't

A bare "Remove" makes a screen-reader user guess which of the page's removable things this one destroys.

Do

The node carries the plus, the label carries the words, and the pair reads as one more station on the route.

Don't

A ghost button at the end of a timeline floats beside the connector instead of sitting on it — the route ends mid-air and the act looks unrelated to the line it extends.

Copy

All of this component's copy is invisible — and it is still copy.

  • Name the objectClose the sheet, Edit the Barkaa stay — the verb and its object, exactly as a labelled button would say it.
  • Both directions for a toggleA disclosure's name flips with its state: Expand when closed, Collapse when open.
  • The tooltip names the actionUse one concise verb such as Edit, Copy, or Close. The accessible name may extend it with the target, but it never describes a different action.
  • The timeline add speaks two wordsAdd stay on the itinerary — the timeline supplies the context a longer label would restate. Its blocked reason sits beside the control in plain lowercase, the same contract as every blocked action.

Accessibility

  • Target floorEvery size at or under 40px carries the invisible expander to 44px on touch. XL is for seats where the whole target should be visible.
  • Always a namearia-label on every instance — there is no visible text to fall back on.
  • The glyph is decorationaria-hidden="true" on the SVG; the name lives on the button.
  • 3:1 for the markThe glyph is held to non-text contrast in both themes — measured, not assumed.
  • State on the controlA disclosure carries aria-expanded; a toggle carries aria-pressed. The rotation is presentation.

When to reach for something else

  • Words fitUse Button. A glyph is the fallback for tight seats, not the preference.
  • A labelled action in an icon-button seatUse the XS button — 28px, the icon-button row's own height — rather than shrinking type into a square.
  • The mark decorates a labelDon't. An icon replaces a label or is absent; a row never ships both for the same fact.
  • The phone's primary actUse a full-width labelled button. An icon button stays a secondary, in-context act on a phone.

Markup

<!-- an act -->
<span class="mk-tooltip-wrap">
  <button class="mk-icon-btn" type="button" aria-label="Edit the item"
          aria-describedby="edit-item-tip">
    <svg data-mk-icon="edit" aria-hidden="true"></svg>
  </button>
  <span class="mk-tooltip" id="edit-item-tip" role="tooltip">Edit</span>
</span>

<!-- a disclosure: filled 20px triangle, muted ink, state on the control -->
<button class="mk-icon-btn" type="button" aria-expanded="false"
        aria-label="Expand the details">
  <svg class="chev" data-mk-icon="chevron" aria-hidden="true"></svg>
</button>

<!-- dense row -->
<button class="mk-icon-btn mk-icon-btn-s" type="button" aria-label="Copy the reference">
  <svg data-mk-icon="copy" aria-hidden="true"></svg>
</button>

<!-- delete: the destructive face -->
<button class="mk-icon-btn mk-icon-btn-destructive" type="button"
        aria-label="Remove the item">
  <svg data-mk-icon="trash" aria-hidden="true"></svg>
</button>

<!-- add stay: the timeline's terminal action — the visible label IS the name -->
<button class="mk-add-stay" type="button">
  <span class="mk-add-stay-mark">
    <svg class="mk-add-stay-icon" data-mk-icon="plus" aria-hidden="true"></svg>
  </span>
  <span class="mk-add-stay-label">Add stay</span>
</button>