RZREDZ.UI

Buttons

A button runs an action; a link drawn as a button goes somewhere. One class, btn, then a tone, a size or an icon when the button needs one.

Examples

The plain btn is neutral. btn-primary marks the one main action of a screen; the other tones say what an action does.

<button class="btn btn-primary" type="button">Save</button>
<button class="btn" type="button">Cancel</button>
<button class="btn btn-secondary" type="button">Duplicate</button>
<button class="btn btn-success" type="button">Publish</button>
<button class="btn btn-danger" type="button">Delete</button>
<button class="btn btn-warning" type="button">Withdraw</button>
<button class="btn btn-info" type="button">Preview</button>

Outline, subtle, text and link

Quieter forms of the same tones for secondary actions, rows and toolbars. btn-text has no fill until hovered; btn-link reads as a link and keeps a button's size, and it alone wraps a long text.

<button class="btn btn-outline-primary" type="button">Outline</button>
<button class="btn btn-outline-danger" type="button">Outline danger</button>
<button class="btn btn-subtle-primary" type="button">Subtle</button>
<button class="btn btn-subtle-danger" type="button">Subtle danger</button>
<button class="btn btn-text" type="button">Text</button>
<button class="btn btn-link" type="button">Link</button>
<button class="btn btn-link text-start" type="button">A long link wraps inside its box, like the title of a card that opens a window</button>

Sizes

A size is a word: btn-xs 24 px, btn-sm 28 px, the default 32 px, btn-lg 40 px - the heights of every control of the same size. btn-xl, 56 px, is for a touch screen and the one action of a page; in a kiosk it grows to a kiosk's size.

<button class="btn btn-primary btn-xs" type="button">Extra small</button>
<button class="btn btn-primary btn-sm" type="button">Small</button>
<button class="btn btn-primary" type="button">Default</button>
<button class="btn btn-primary btn-lg" type="button">Large</button>
<button class="btn btn-primary btn-xl" type="button">Extra large</button>

Icons

An icon sits beside the words, or alone in a square btn-icon whose name is its aria-label. btn-close draws the close glyph by itself.

Open the site
<button class="btn" type="button"><i class="bi bi-plus-lg" aria-hidden="true"></i>New excursion</button>
<a class="btn" href="#btn-icons">Open the site<i class="bi bi-box-arrow-up-right" aria-hidden="true"></i></a>
<button class="btn btn-icon" type="button" aria-label="Edit"><i class="bi bi-pencil" aria-hidden="true"></i></button>
<button class="btn btn-icon btn-text" type="button" aria-label="More actions"><i class="bi bi-three-dots" aria-hidden="true"></i></button>
<button class="btn btn-icon btn-sm btn-subtle-danger" type="button" aria-label="Remove"><i class="bi bi-trash3" aria-hidden="true"></i></button>
<button class="btn-close" type="button" aria-label="Close"></button>

Groups and toolbars

btn-group joins buttons into one control - here a choice of view, each button pressed or not. btn-toolbar lays groups and buttons out in a wrapping row.

<div class="btn-toolbar" role="group" aria-label="List view">
	<div class="btn-group" role="group" aria-label="Layout">
		<button class="btn" type="button" aria-pressed="true">List</button>
		<button class="btn" type="button" aria-pressed="false">Board</button>
		<button class="btn" type="button" aria-pressed="false">Calendar</button>
	</div>
	<div class="btn-group" role="group" aria-label="Export">
		<button class="btn" type="button">Export</button>
		<button class="btn btn-icon" type="button" aria-label="Export options"><i class="bi bi-chevron-down" aria-hidden="true"></i></button>
	</div>
</div>

A choice of one

A segmented choice is Bootstrap's: in a btn-group, each input type="radio" class="btn-check" before its label class="btn". The radio stays the control - the arrow keys move the choice, the form sends it - and the checked one is drawn pressed, an outline tone filled with its colour; the focus ring goes on the label. A link or a button that is the current one of a group carries aria-current and is drawn pressed too.

<div class="btn-toolbar">
	<div class="btn-group" role="radiogroup" aria-label="Period">
		<input class="btn-check" type="radio" name="period" id="btn-segmented-day" value="day">
		<label class="btn btn-outline-primary" for="btn-segmented-day">Day</label>
		<input class="btn-check" type="radio" name="period" id="btn-segmented-week" value="week" checked>
		<label class="btn btn-outline-primary" for="btn-segmented-week">Week</label>
		<input class="btn-check" type="radio" name="period" id="btn-segmented-month" value="month">
		<label class="btn btn-outline-primary" for="btn-segmented-month">Month</label>
	</div>
	<div class="btn-group" role="radiogroup" aria-label="Status">
		<input class="btn-check" type="radio" name="state" id="btn-segmented-all" value="all" checked>
		<label class="btn" for="btn-segmented-all">All</label>
		<input class="btn-check" type="radio" name="state" id="btn-segmented-open" value="open">
		<label class="btn" for="btn-segmented-open">Open</label>
		<input class="btn-check" type="radio" name="state" id="btn-segmented-closed" value="closed">
		<label class="btn" for="btn-segmented-closed">Closed</label>
	</div>
	<nav class="btn-group" aria-label="Year">
		<a class="btn btn-sm" href="#btn-segmented">2025</a>
		<a class="btn btn-sm" href="#btn-segmented" aria-current="page">2026</a>
	</nav>
</div>

btn-group-vertical stacks the buttons of a group, each as wide as the widest.

<div class="btn-group-vertical" role="group" aria-label="Order">
	<button class="btn" type="button"><i class="bi bi-arrow-up" aria-hidden="true"></i>Move up</button>
	<button class="btn" type="button"><i class="bi bi-arrow-down" aria-hidden="true"></i>Move down</button>
	<button class="btn" type="button"><i class="bi bi-trash" aria-hidden="true"></i>Remove</button>
</div>

Toggle and busy

A toggle carries aria-pressed. While its action runs, REDZ.ui.busy(button, promise) shows a spinner in the button and marks it aria-disabled: it keeps the focus and ignores clicks until the promise settles. See Busy for the words a button says while it waits.

<button class="btn" type="button" aria-pressed="false" data-docs-run="toggle"><i class="bi bi-pin-angle" aria-hidden="true"></i>Pin</button>
<button class="btn btn-primary" type="button" data-docs-run="busy-button">Save for 2 seconds</button>

A wait before it works again

data-cooldown="N" makes a button wait N seconds: it is aria-disabled - it keeps the focus and ignores clicks - and shows the seconds left after its words, in the page's text cooldown; then it wakes and the attribute goes. Setting the attribute again starts the wait again; a page reloaded during a wait renders the seconds left. The count is drawn, not read out every second: say the wait in words once, when it starts - "A new code is on its way; ask again in a minute". A cooldown set while the button is busy keeps it disabled after the wait, until its count ends.

<button class="btn" type="button" data-cooldown="20" data-docs-run="cooldown"><i class="bi bi-arrow-clockwise" aria-hidden="true"></i>Send the code again</button>

States

A state is the attribute accessibility needs anyway, on the button itself: no active or disabled classes. aria-expanded is set by the browser on a button that opens a menu or a dialog.

classrest:hover:focus-visiblearia-pressedaria-expandeddisabledaria-disabledaria-busy
btn
btn-primary
btn-danger
btn-outline-primary
btn-subtle-primary
btn-subtle-secondary
btn-subtle-success
btn-subtle-danger
btn-subtle-warning
btn-subtle-info
btn-text
btn-link
btn-close - - - -

Keyboard and accessibility

  • <button type="button"> for an action, <a href> for a place; btn draws both the same. Enter and Space press a button, Enter follows a link.
  • A button with only an icon takes its name from aria-label; the icon is aria-hidden="true".
  • disabled takes a button out of the tab order and hides why. aria-disabled="true" keeps it focusable - so a tooltip can say why - and the library swallows its clicks.
  • A button that opens a menu or a dialog uses the browser's command: commandfor + command, never data-bs-toggle.
  • A segmented choice is radios: one Tab stop, the arrow keys move the choice, a screen reader says "1 of 3". A group of toggles that each stand alone is buttons with aria-pressed.
  • The focus ring is 2 px of --redz-focus, 3:1 against the page in both themes.

Tokens

From css/components/btn.css. It declares --redz-btn-bg, --redz-btn-fg, --redz-btn-hover, --redz-btn-hover-fg, --redz-btn-on-bg, --redz-btn-on-fg on its root, never on rows or items.

It reads the theme's --redz-bg-hover, --redz-text, --redz-control-height, --redz-space-12, --redz-radius-8, --redz-focus, --redz-bg-page, --redz-text-faint, --redz-primary, --redz-primary-contrast, --redz-secondary, --redz-secondary-contrast, --redz-success, --redz-success-contrast, --redz-danger, --redz-danger-contrast, --redz-warning, --redz-warning-contrast, --redz-info, --redz-info-contrast, --redz-primary-subtle, --redz-secondary-subtle, --redz-success-subtle, --redz-danger-subtle, --redz-warning-subtle, --redz-info-subtle, --redz-control-height-xs, --redz-radius-6, --redz-fs-12, --redz-control-height-sm, --redz-space-8, --redz-control-height-lg, --redz-space-16, --redz-fs-16, --redz-space-24, --redz-radius-12, --redz-fs-20, --redz-control-height-float, --redz-text-muted, --redz-motion.

From Bootstrap 5.3

Bootstrap 5.3REDZ.UIChangeNote
btn-close-white-removethe close glyph takes the text color
btn-dark-reviewno light or dark buttons: btn or btn-text
btn-light-reviewno light or dark buttons: btn or btn-text
btn-outline-dark-reviewbtn-outline-secondary
btn-outline-light-reviewbtn-outline-secondary
data-bs-toggle="button"aria-pressedreviewswitch aria-pressed in your click handler