Skip to content

Button

Buttons trigger actions or navigate to new pages. They tell the user what will happen next and visually communicate the importance and risk of that action.

Component

Use fd-button to let users take actions — confirming decisions, navigating to new pages, submitting forms, or triggering client-side operations. Six variants communicate the weight and risk of each action, including an inverted subtle treatment for dark surfaces. The component renders a native <button type="button"> by default, a submit-capable button when type="submit" is set, or an <a> when an href is provided.

When to use

  • The user needs to take an action — Confirming a decision, starting a process, opening a dialog, or navigating to a destination that feels like an action ("Apply now," "Download report").
  • You need to communicate the importance of an action — Primary for the main action, neutral or subtle for secondary actions, destructive for irreversible operations.
  • You need a link that looks and feels like a button — Set the href attribute and fd-button renders an <a> element, preserving link semantics (right-click, cmd-click, screen reader link lists) while providing button styling.

When not to use

  • Don't use buttons for inline navigation — If the text sits inside a paragraph and navigates to another page, use a regular link. Buttons are for actions that feel like decisions, not for inline references.
  • Don't use buttons for toggling state — Checkboxes, radio buttons, switches, and toggle buttons are better for on/off or selection patterns.
  • Don't use a destructive button without a confirmation step — In a government/regulatory context, irreversible actions must give the user a chance to reconsider. Pair destructive buttons with confirmation dialogs or two-step flows.

Examples

Variant comparison — primary, neutral, subtle, subtle inverted, outline, and destructive. Open Storybook to switch variants and states with controls. View in Storybook →
Common patterns — icons, icon-only actions, link mode, and loading state in one compact preview. Open Storybook for the full set of dedicated examples. View in Storybook →
Form action rows use `fd-button type="submit"` for the primary submit action and `type="button"` for non-submitting actions. View in Storybook →

Properties

NameTypeDefaultDescription
variant"primary" | "neutral" | "subtle" | "subtle-inverted" | "outline" | "destructive"primaryVisual treatment for the action. Use subtle-inverted on dark surfaces and destructive only for high-risk actions.
disabledbooleanfalseMakes the control inert. In link mode, fd-button uses aria-disabled="true" and suppresses navigation.
type"button" | "submit" | "reset"buttonControls button-mode behavior. Use submit for primary form submission. reset remains deferred and renders as button.
hrefstring | undefinedundefinedWhen set, fd-button renders a native <a> instead of an internal <button>.
targetstring | undefinedundefinedNative link target. Applies only when href is set.
relstring | undefinedundefinedNative link relationship tokens. When target="_blank", fd-button always adds noopener noreferrer.
loadingbooleanfalseShows a spinner and makes the control inert while work is in progress.
loading-labelstring | undefinedundefinedOptional replacement label to show and announce while loading is true.

fd-button type="submit" participates in the host form as the recommended primary submit action. type="reset" is intentionally deferred by ADR-009 and renders as <button type="button"> for now.

Slots

NameDescription
(default)Visible button label
icon-startLeading icon content, typically fd-icon
icon-endTrailing icon content, typically fd-icon

CSS custom properties

NameDefaultDescription
--fd-button-gapvar(--fdic-spacing-2xs, 4px)Gap between label and visual content
--fd-button-height44pxMinimum button height
--fd-button-min-width44pxMinimum button width
--fd-button-radiusvar(--fdic-corner-radius-sm, 3px)Corner radius
--fd-button-font-sizevar(--fdic-font-size-body, 18px)Button font size
--fd-button-icon-only-sizevar(--fd-button-height, 44px)Square size for icon-only buttons
--fd-button-icon-edge-padding11pxExtra inline padding when a leading or trailing icon is present
--fd-button-focus-gapvar(--fdic-color-bg-input, #ffffff)Inner gap color in the focus ring
--fd-button-focus-ringvar(--fdic-color-border-input-focus, #38b6ff)Outer focus-ring color
--fd-button-bg-primaryvar(--fdic-color-bg-active, #0d6191)Primary background color
--fd-button-text-primaryvar(--fdic-color-text-inverted, #ffffff)Primary text color
--fd-button-bg-destructivevar(--fdic-color-bg-destructive, #d80e3a)Destructive background color
--fd-button-text-destructivevar(--fdic-color-text-inverted, #ffffff)Destructive text color
--fd-button-bg-neutralvar(--fdic-color-bg-interactive, #f5f5f7)Neutral background color
--fd-button-text-neutralvar(--fdic-color-text-primary, #212123)Neutral text color
--fd-button-text-subtlevar(--fdic-color-text-primary, #212123)Subtle text color
--fd-button-text-subtle-invertedvar(--fdic-color-text-inverted, #ffffff)Subtle inverted text and icon color
--fd-button-text-outlinevar(--fdic-color-text-link, #1278b0)Outline text color
--fd-button-border-outlinevar(--fdic-color-bg-active, #0d6191)Outline border color
--fd-button-overlay-hovervar(--fdic-color-overlay-hover, rgba(0, 0, 0, 0.04))Hover overlay color
--fd-button-overlay-activevar(--fdic-color-overlay-pressed, rgba(0, 0, 0, 0.08))Active overlay color
--fd-button-overlay-hover-invertedrgba(255, 255, 255, 0.12)Hover overlay color for subtle-inverted
--fd-button-overlay-active-invertedrgba(255, 255, 255, 0.18)Active overlay color for subtle-inverted
--fd-button-bg-disabledvar(--fdic-color-bg-container, #f5f5f7)Disabled background color
--fd-button-text-disabledvar(--fdic-color-text-disabled, #9e9ea0)Disabled text color
--fd-button-border-outline-disabledvar(--fdic-color-border-input-disabled, #d6d6d8)Disabled outline border color
--fd-button-spinner-size1emSpinner size in loading state
--fd-button-spinner-speed0.8sSpinner rotation duration

Shadow parts

NameDescription
baseInternal native <button> or <a> element
labelVisible label wrapper
spinnerLoading spinner wrapper
  • Primary carries the main action on the page or section. Limit it to one primary button per section.
  • Subtle inverted is for low-emphasis actions on dark surfaces such as mastheads, overlays, and other inverted shells.
  • Destructive is for irreversible actions like deleting records or revoking access. Pair it with a confirmation step.
  • Icon-only buttons require an accessible name on fd-button, such as aria-label.
  • Icon-only buttons render as square controls with the icon centered horizontally and vertically.
  • Link mode uses href and renders a native <a>, preserving link semantics.
  • Loading prevents duplicate activation while the action is in progress. Use loading-label when the wait may be noticeable.

Best practices

Do

Use one primary button per section

A single primary button creates a clear visual hierarchy and tells the user where the main action is.

Don't

Use multiple primary buttons in the same section

Competing primary buttons force the user to evaluate equal-weight options without guidance. Use neutral or outline for secondary actions.

Do

Use specific verb labels

"Submit filing," "Download report," "Revoke access" — the user knows exactly what will happen.

Don't

Use vague labels like "Click here" or "Submit"

Generic labels leave the user guessing. In a regulatory context, clarity about the action builds trust and reduces errors.

Do

Pair destructive buttons with a confirmation step

A dialog or two-step flow gives the user a chance to reconsider before an irreversible action.

Don't

Let a single click trigger an irreversible action

In government systems, accidental deletions or revocations can have legal and financial consequences. Always confirm.

Content guidelines

Start labels with a verb.

Buttons are actions. Lead with what happens when the user clicks.

Do

Submit filing

Don't

Filing submission

Keep labels short — 1 to 3 words.

Buttons should be scannable at a glance. Move additional context to surrounding text.

Do

Export data

Don't

Export all quarterly data to CSV format

Use sentence case.

Sentence case is easier to read and feels less formal than title case, which is important for approachability in public-facing government tools.

Do

Download report

Don't

Download Report

Loading state

Use the loading attribute when an action is in progress and you need to prevent duplicate submissions — for example, after the user clicks "Submit filing" and the request is still processing.

  • Loading is not disabled. Loading means "your action is in progress, please wait." Disabled means "this action is not available right now." Use the right one.
  • The label stays visible by default. The spinner appears alongside the text so the user can still read what action is pending.
  • Use loading-label for long-running actions. If the wait may be noticeable, changing the label can improve clarity: loading-label="Submitting…" while the original label is "Submit filing."
  • Icon-only buttons show the spinner in place of the icon. The accessible name (aria-label) is preserved.
  • Link-mode buttons (href) suppress navigation while loading using the same inert pattern as disabled links.
  • Reduced motion: The spinner animation is suppressed under prefers-reduced-motion: reduce. The static spinner icon and aria-busy state still communicate progress.

Accessibility

  • fd-button renders a native <button> element inside its shadow DOM. Native buttons are focusable, keyboard-operable (Enter and Space), and announced correctly by screen readers without extra ARIA.
  • When href is set, the component renders a native <a> element instead. This preserves link semantics — screen reader link lists, right-click context menus, and cmd/ctrl-click for new tabs all work as expected.
  • Icon-only buttons require a name on fd-button — Set aria-label or aria-labelledby on the fd-button element itself, not on the fd-icon inside it. The component forwards that accessible name to the internal native control, and the icon should remain decorative (aria-hidden="true"). Icon-only buttons render as square controls so the icon remains centered and the hit target stays predictable.
  • Disabled state: On <button>, the native disabled attribute is used. On <a> (link mode), aria-disabled="true" is set instead, since anchor elements don't support native disabled.
  • External link safety: When link-mode buttons use target="_blank", fd-button adds rel="noopener noreferrer" automatically and preserves any extra rel tokens you supply.
  • All interactive states use the standard focus ring: outline: 2px solid with outline-offset: 2px on :focus-visible.
  • Loading state: When loading is set, the native control becomes inert (disabled on <button>, aria-disabled="true" on <a>) and receives aria-busy="true" as a supplemental signal. The spinner icon is decorative (aria-hidden="true"). If loading-label is provided, the accessible name updates to match the visible loading label, and any aria-labelledby on the host is suppressed so the loading label takes precedence. When loading-label is not set, aria-labelledby is forwarded normally.

Known limitations

  • Reset behavior is deferredfd-button type="reset" currently renders an internal <button type="button"> and does not reset the form. Use reset or clear actions only when the workflow has a strong, documented reason; consequential forms should usually avoid them.
  • aria-busy AT coverage variesaria-busy on <button> is not consistently announced across all screen reader / browser combinations. The primary inert signal is the native disabled attribute (or aria-disabled for links), with aria-busy as supplemental. Verify with your target AT combinations.