A debounced action button with four variants, five sizes, and built-in loading, disabled, and full-width states — plus, since 21.4.0, a [gogButton] directive that gives the same look to a link you own.
severity says what the action means; variant says how loudly it is drawn. The two are orthogonal, so this is not a fifth variant — every cell below is a real combination, and a ghost delete is still a delete. "accent" is the default and the absence of a claim, which is why every other button on this page needs no opt-out.
success
danger
warning
info
Now switch the theme with the toggle in the header and look again. This grid is the best demonstration of the token layering the Theming guide describes, because three separate decisions are visible in it at once. A filled button's label is the colour its own theme states for that status, and it is stated per status rather than per theme: under primeng, success, warning and info carry a near-black label because white would be unreadable on those three, while danger keeps white because on that hue it is fine. Hover and press deepen the fill away from that label, so a state always makes the label easier to read rather than harder. And the outlined and ghost labels are not the raw status colour at all but that hue mixed halfway toward the page's ink, because as body text the raw hue clears WCAG AA in only five of the eleven shipped themes.
Hold any of these down. The background deepens one step past its own hover, so the press reads even while the pointer is already hovering the button — and it is a state rather than a movement, which is the whole point. Turn on prefers-reduced-motion: reduce (DevTools → Rendering → Emulate CSS media feature) and the scale() goes away while the colour stays.
Until 21.9.0 the scale was the whole press, so a reader with animations off pressed a button and nothing happened at all. The ripple does not cover that case and is not meant to: it is off by default, and it is suppressed under reduced motion on purpose, because a ripple really is decoration and a press state is not. Override the colour per instance with --gog-button-press-bg / --gog-button-press-color, per theme with --gog-button-<variant>-active-bg, and retime or remove the movement with --gog-button-active-scale. Every other pressable surface in the library — menu items, chips, tab and accordion headers, button-toggle options, the three dropdowns' option rows — gained the same treatment in the same release.
<!-- Nothing to wire up: every variant presses. The tokens are the knobs. --><gog-buttonvariant="primary">primary</gog-button><!-- One instance, its own press colour --><gog-buttonvariant="primary"style="--gog-button-press-bg: var(--gog-danger-color); --gog-button-press-color: #fff"
>
primary
</gog-button>
Disabled
One disabled button per variant — disabled state must stay legible on every color.
Same trap as ariaLabel, one step further: the component hides the real <button>, so [attr.aria-pressed] written on <gog-button> lands on the custom-element host — which has no role — and reaches no assistive tech at all. It compiles, throws nothing, and looks right, which is what makes it worth a demo. Use the inputs: ariaPressed, ariaExpanded, ariaControls and ariaHasPopup.
A toggle button now looks toggled21.9.0 — press "Mirror layout" and it keeps an inset ring (--gog-button-<variant>-toggled-shadow, width from --gog-button-toggled-ring-width) for as long as aria-pressed is "true" or "mixed". A ring rather than a fill, because hover and press already own the background and the state has to survive both. Until 21.9.0 it did not exist at all: a button could announce itself as on to a screen reader and look identical to an off one.
The region ariaControls points at. It stays in the document and hides, rather than being removed — aria-controls has to name an element that exists.
Inspect either button: the attribute sits on the inner <button>, never on <gog-button>. The off state renders aria-pressed="false" rather than dropping the attribute — null means "not a toggle button", false means "a toggle button that is off", and to a screen reader those are two different controls. [gogButton] needs none of these inputs: it styles an element you own, so write the attributes on your own <button> directly — and it draws the same toggled ring off the attribute you wrote.
Clicks are throttled leading-edge: the first click fires immediately, further clicks are dropped for debounce ms (default 300). Click rapidly and watch the counter lag behind your clicks.
Accepted clicks: 0
<gog-buttonvariant="primary" [debounce]="300" (gogClick)="onSpamClick()">Click me fast</gog-button>
gog-button renders its own <button>, so it can never be a link. The directive inverts that: the element stays yours and [gogButton] only gives it the look.
The selector is a[gogButton], button[gogButton] — deliberately not a bare [gogButton]. On a <div> the result would look like a button while being invisible to the keyboard and to assistive technology.
Which one to reach for
The component when the button acts on the page: it owns loading (a centred spinner it projects), debounce click throttling and the gogClick output — none of which a bare element can provide. The directive when the element must be a link, or must keep directives of its own. routerLink, href, target, download, type="submit" and the rest keep working because they were never brokered through an input in the first place.
That is also why the library still has no @angular/router dependency — a design point, not an omission. A component that took a routerLink input would force the router on every app that installs the package.
Two things the directive deliberately does not do: no disabled on an <a> (there is no such thing — drop the href or render a real <button>), and no loading state (the spinner is a projected child, which a directive cannot add without taking over the element's content).
Its styles live in the global styles/button.css, pulled in by index.css, because Angular's emulated encapsulation could never reach an element declared in your template. Nothing changes in your setup — see Theming.
[gogButton] — Inputs
Name
Type
Default
Description
variant
'primary' | 'secondary' | 'outline' | 'ghost'
'primary'
Visual style — the same four the component offers.
What the action means, as opposed to how loudly it is drawn. Orthogonal to variant, so every combination is real: a ghost delete is still a delete. accent is the absence of a claim. The same input the component takes.
size
'xsm' | 'sm' | 'md' | 'lg' | 'slg'
'md'
Also settable app-wide via GOG_CONFIG.control.size.
fullWidth
boolean
false
Stretches the element to fill its container. A bare attribute works.
What the action means, as opposed to how loudly it is drawn. Orthogonal to variant, so every combination is real: a ghost delete is still a delete. accent is the absence of a claim and leaves the button exactly as it was.
size
'xsm' | 'sm' | 'md' | 'lg' | 'slg'
'md'
Button size.
disabled
boolean
false
Fully non-interactive: excluded from tab order via the native disabled attribute.
fullWidth
boolean
false
Stretches the button to fill its container.
type
'button' | 'submit' | 'reset'
'button'
Forwarded to the native <button> type attribute.
loading
boolean
false
Shows a spinner in place of the label and blocks activation. Uses aria-disabled rather than the native disabled attribute, so the button stays focusable.
debounce
number
300
Minimum time, in ms, between accepted clicks. Leading-edge throttle: the first click fires immediately, further clicks are dropped until the window elapses.
ariaLabel
string | null
null
Accessible name forwarded to the native <button>. Required for icon-only buttons — a plain aria-label attribute on <gog-button> lands on the host element, not the inner button, so assistive tech never sees it.
ariaPressed21.8.0
boolean | 'mixed' | null
null
Marks the button as a toggle and reports its state. null omits the attribute entirely; false renders aria-pressed="false", which is what an off toggle has to say — a button with no aria-pressed is not a toggle button.
ariaExpanded21.8.0
boolean | null
null
For a disclosure or popup trigger: whether the thing it controls is currently open. Like ariaPressed, false is a real state and null means "this button expands nothing".
ariaControls21.8.0
string | null
null
Id of the element this button controls. Pairs with ariaExpanded; point it at an element that is actually in the document.
ariaHasPopup21.8.0
GogAriaHasPopup | null
null
boolean | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog' — what kind of popup the button opens.
ripple21.6.1
boolean | undefined
undefined
Press ripple on the inner <button>. Unset, falls back to GOG_CONFIG.ripple.enabled, which is off by default; setting it here wins over the app-wide value in both directions.
Outputs
Name
Type
Description
gogClick
EventEmitter<MouseEvent>
Emitted on each accepted click, after debounce throttling.
Global Configuration
Set any of these once with provideGogConfig() instead of repeating them on every instance — an instance's own input still wins when it sets one itself. See Global Configuration for the full reference.
GOG_CONFIG.control.size
GOG_CONFIG.button.debounce
GOG_CONFIG.ripple.enabled
GOG_CONFIG.spinner.component — the spinner loading draws, which has no input of its own
GOG_CONFIG.spinner.variant — the same spinner, when no component is set
This site is not on the default here. The library ships ripple.enabled as false; these docs set it to true app-wide so the demos above actually show the press feedback. In a fresh app you get no ripple until you ask for one — the droplet button in the header switches this site between the two, and it is on right now.
Styling Tokens
Every CSS custom property the button paints with. Override any of them — on a single instance, a subtree, or a theme — to restyle it. See the Theming guide for the full token-layering model, or the Theme Generator to tweak these live.
The keyboard focus ring. Its colour is its own token since 21.13.0 and reads the accent; it used to follow each variant’s hover wash, which on ghost and the severity outline buttons made the ring invisible in four themes (1.07:1 at worst).
--gog-button-active-scale
How far a press shrinks the button. Dropped under prefers-reduced-motion, where the press colour carries the state on its own.
The severity palette, per status (danger/success/warning/info). fill and on-fill are a filled button and its label; ink is the label of a transparent one, the status hue mixed halfway toward the page ink; wash is the hover background under it.