IconButton

A square button that shows only an icon (also called an icon-only button): refresh, export, close, the actions of a row. Reach for it where the icon is known well enough to stand alone; its name is required and shows as a tooltip.

import { IconButton } from "@umriss-ui/core";

Its aria-label is required, and it is the accessible name and the tooltip in one: a screen reader hears it once, and the eye reads it on hover or keyboard focus.

The icon is yours: an SVG, a glyph of the set, or an icon font's element. The button sets its size – 14 px, 12 px at sm – the size an SVG takes beside a label in a Button as well.

It is a Button underneath, with the same sizes, loading state and variants; the default is plain, quiet and neutral. As the trigger of a Menu it anchors the panel, and its tooltip gives way while the menu is open.

Examples

Name the icon

aria-label is required: it is the name a screen reader announces and the tooltip a sighted reader sees on hover or keyboard focus.

import { IconButton } from "@umriss-ui/core";

function Refresh() {
  return (
    <svg viewBox="0 0 10 10" aria-hidden="true">
      <path d="M8.3 5.8A3.4 3.4 0 1 1 7.6 2.7M7.9 1.2v1.8H6.1" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

export default function NameTheIcon() {
  return (
    <IconButton aria-label="Refresh the tours">
      <Refresh />
    </IconButton>
  );
}

Variants

plain is the default – quiet and neutral, for a bar or a row. ghost is quiet in the accent; the others mean what they mean on a Button.

import { CrossGlyph, IconButton, PlusGlyph, Stack } from "@umriss-ui/core";

function Export() {
  return (
    <svg viewBox="0 0 10 10" aria-hidden="true">
      <path d="M5 1.5v5M2.8 4.3 5 6.5l2.2-2.2M1.8 8.5h6.4" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

function Check() {
  return (
    <svg viewBox="0 0 10 10" aria-hidden="true">
      <path d="M1.8 5.3 4 7.5l4.2-5" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

export default function Variants() {
  return (
    <Stack direction="row" gap={3} wrap align="center">
      <IconButton aria-label="Export the tour list">
        <Export />
      </IconButton>
      <IconButton variant="ghost" aria-label="Add a stop">
        <PlusGlyph />
      </IconButton>
      <IconButton variant="secondary" aria-label="Export the tour list">
        <Export />
      </IconButton>
      <IconButton variant="primary" aria-label="Release the tour">
        <Check />
      </IconButton>
      <IconButton variant="danger" aria-label="Cancel the tour">
        <CrossGlyph />
      </IconButton>
    </Stack>
  );
}

Sizes

The heights of Button, and the same icon size beside a label and alone: 14 px at md, 12 px at sm, in header bars and table rows.

import { Button, IconButton, Stack } from "@umriss-ui/core";

function Export() {
  return (
    <svg viewBox="0 0 10 10" aria-hidden="true">
      <path d="M5 1.5v5M2.8 4.3 5 6.5l2.2-2.2M1.8 8.5h6.4" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

export default function Sizes() {
  return (
    <Stack gap={3}>
      <Stack direction="row" gap={2} align="center">
        <Button>
          <Export />
          Export
        </Button>
        <IconButton variant="secondary" aria-label="Export the tour list">
          <Export />
        </IconButton>
      </Stack>
      <Stack direction="row" gap={2} align="center">
        <Button size="sm">
          <Export />
          Export
        </Button>
        <IconButton size="sm" variant="secondary" aria-label="Export the tour list">
          <Export />
        </IconButton>
      </Stack>
    </Stack>
  );
}

Bring your own icons

A glyph of the set, an SVG drawn for 24 px as lucide and Font Awesome ship theirs, a character the way an icon font sets it – the button gives each the same size.

import { CrossGlyph, IconButton, Stack } from "@umriss-ui/core";

/* Drawn on a 24 grid with its own width and height, as icon sets ship them. */
function Printer() {
  return (
    <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M6 9V2h12v7" />
      <path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2" />
      <path d="M6 14h12v8H6z" />
    </svg>
  );
}

export default function BringYourOwnIcons() {
  return (
    <Stack direction="row" gap={3} align="center">
      <IconButton variant="secondary" aria-label="Remove the filter">
        <CrossGlyph />
      </IconButton>
      <IconButton variant="secondary" aria-label="Print the delivery notes">
        <Printer />
      </IconButton>
      <IconButton variant="secondary" aria-label="Sort by departure">
        <span aria-hidden="true">⇅</span>
      </IconButton>
    </Stack>
  );
}

Loading and disabled

While its action runs, the spinner takes the icon's place and the button locks; disabled, it fades and keeps its name.

import { useEffect, useState } from "react";
import { IconButton, Stack, Text } from "@umriss-ui/core";

function Refresh() {
  return (
    <svg viewBox="0 0 10 10" aria-hidden="true">
      <path d="M8.3 5.8A3.4 3.4 0 1 1 7.6 2.7M7.9 1.2v1.8H6.1" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

function Export() {
  return (
    <svg viewBox="0 0 10 10" aria-hidden="true">
      <path d="M5 1.5v5M2.8 4.3 5 6.5l2.2-2.2M1.8 8.5h6.4" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

export default function LoadingAndDisabled() {
  const [loading, setLoading] = useState(false);
  const [refreshed, setRefreshed] = useState("10:30");

  useEffect(() => {
    if (!loading) return;
    const timer = setTimeout(() => {
      setLoading(false);
      setRefreshed("10:31");
    }, 1500);
    return () => clearTimeout(timer);
  }, [loading]);

  return (
    <Stack direction="row" gap={2} align="center">
      <IconButton variant="secondary" aria-label="Refresh the tours" loading={loading} onClick={() => setLoading(true)}>
        <Refresh />
      </IconButton>
      <IconButton variant="secondary" aria-label="Export the tour list" disabled>
        <Export />
      </IconButton>
      <Text size="sm" tone="muted">
        Refreshed at {refreshed}
      </Text>
    </Stack>
  );
}

Build a toolbar

Plain buttons for the bar, a group for the pair that belongs together, a menu for the rest – every square the same size, every name a tooltip.

import { useState } from "react";
import { ButtonGroup, IconButton, Menu, MenuItem, MenuSeparator, MinusGlyph, PlusGlyph, Stack, Text } from "@umriss-ui/core";

const ZOOM = [50, 75, 100, 150, 200];

function Refresh() {
  return (
    <svg viewBox="0 0 10 10" aria-hidden="true">
      <path d="M8.3 5.8A3.4 3.4 0 1 1 7.6 2.7M7.9 1.2v1.8H6.1" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

function Export() {
  return (
    <svg viewBox="0 0 10 10" aria-hidden="true">
      <path d="M5 1.5v5M2.8 4.3 5 6.5l2.2-2.2M1.8 8.5h6.4" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

export default function Toolbar() {
  const [zoom, setZoom] = useState(2);

  return (
    <Stack direction="row" gap={3} align="center" justify="space-between" wrap style={{ maxWidth: 620 }}>
      <Text size="sm" weight="semibold">
        Tours, Tuesday 17 March
      </Text>
      <Stack direction="row" gap={2} align="center">
        <ButtonGroup aria-label="Zoom the tour plan">
          <IconButton size="sm" variant="secondary" aria-label="Zoom out" disabled={zoom === 0} onClick={() => setZoom(zoom - 1)}>
            <MinusGlyph />
          </IconButton>
          <IconButton size="sm" variant="secondary" aria-label="Zoom in" disabled={zoom === ZOOM.length - 1} onClick={() => setZoom(zoom + 1)}>
            <PlusGlyph />
          </IconButton>
        </ButtonGroup>
        <Text as="span" size="xs" mono tone="muted" style={{ width: "4ch" }}>
          {ZOOM[zoom]}%
        </Text>
        <IconButton size="sm" aria-label="Refresh the tours">
          <Refresh />
        </IconButton>
        <IconButton size="sm" aria-label="Export the tour list">
          <Export />
        </IconButton>
        <Menu
          align="end"
          trigger={
            <IconButton size="sm" aria-label="More actions for the tours">
              ⋯
            </IconButton>
          }
        >
          <MenuItem>Print the delivery notes</MenuItem>
          <MenuItem>Copy the link</MenuItem>
          <MenuSeparator />
          <MenuItem tone="danger">Cancel every open tour</MenuItem>
        </Menu>
      </Stack>
    </Stack>
  );
}

When to use something else

Keyboard

Key Action
Tab Reaches the button and shows its name.
Enter / Space Presses it.
Escape Hides the name.

API

IconButtonProps

NameTypeDefaultDescription
childrenrequiredReactNode—The icon: an SVG, a glyph of the set or an icon font's element. The button sets its size - --u-icon-size, --u-icon-size-sm at sm.

Shown in: Configurator, Name the icon, Variants and 5 more

variantButtonVariant
| "primary"
| "secondary"
| "ghost"
| "plain"
| "danger"
"plain"How loud the button is; plain is quiet and neutral, and the rest mean what they mean on a Button.

Shown in: Configurator, Variants, Sizes and 3 more

sizeButtonSize
"sm" | "md"
the size of a ControlSizeProvider around it, else "md"sm for buttons in header bars and table rows, md otherwise. from ButtonProps

Shown in: Configurator, Sizes, Build a toolbar and 57 more

loadingbooleanfalseShows a loading indicator and locks the button. from ButtonProps

Shown in: Configurator, Loading and disabled, Configurator (Button) and 4 more

refRef<HTMLButtonElement>—Receives the component's root element. from RefProp

Shown in: A row that holds still (Sizes), An anchored surface (Popover), Take the width of the trigger (Popover) and 2 more

Accessibility

NameTypeDefaultDescription
aria-labelrequiredstring—The name: what a screen reader announces and the tooltip shows. Required - an icon alone names nothing.

Shown in: Configurator, Name the icon, Variants and 5 more

Also takes every attribute of <button>.

Types on this page

ButtonVariant

How loud a button is: primary for the one main action, secondary by default, ghost quiet in the accent, plain quiet and neutral, danger for a destructive action.

type ButtonVariant = "primary" | "secondary" | "ghost" | "plain" | "danger";

ButtonSize

A button's two heights - the controls' (ControlSize).

type ButtonSize = ControlSize;

Resolves to "sm" | "md".

ControlSize

The two heights of a control: sm for a toolbar and dense forms, md otherwise.

type ControlSize = "sm" | "md";

Known limits

What umriss deliberately does not build, and why: ADR-0032.

Every page of @umriss-ui/core

Getting started

Customising

Layout

Typography

Actions

Forms

Choosing

Dates and times

Feedback

Overlays

Navigation

Data display

API index