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.
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
);
}
| Key | Action |
|---|---|
Tab |
Reaches the button and shows its name. |
Enter / Space |
Presses it. |
Escape |
Hides the name. |
IconButtonProps| Name | Type | Default | Description |
|---|---|---|---|
childrenrequired | ReactNode | — | 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 |
variant | ButtonVariant| "primary" | "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 |
size | ButtonSize"sm" | "md" | the size of a ControlSizeProvider around it, else "md" | sm for buttons in header bars and table rows, md otherwise. from ButtonPropsShown in: Configurator, Sizes, Build a toolbar and 57 more |
loading | boolean | false | Shows a loading indicator and locks the button. from ButtonPropsShown in: Configurator, Loading and disabled, Configurator (Button) and 4 more |
ref | Ref<HTMLButtonElement> | — | Receives the component's root element. from RefPropShown in: A row that holds still (Sizes), An anchored surface (Popover), Take the width of the trigger (Popover) and 2 more |
| Name | Type | Default | Description |
|---|---|---|---|
aria-labelrequired | string | — | 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>.
ButtonVariantHow 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";ButtonSizeA button's two heights - the controls' (ControlSize).
type ButtonSize = ControlSize;Resolves to "sm" | "md".
ControlSizeThe two heights of a control: sm for a toolbar and dense forms, md
otherwise.
type ControlSize = "sm" | "md";Tooltip around a Button.Button with a label.What umriss deliberately does not build, and why: ADR-0032.