.pds-button {
--button-font-size: var(--pds-typography-size-s);
--button-height: var(--pds-spacing-button-height-s);
--button-padding-inline: var(--pds-spacing-button-padding-inline-s);
--button-gap-inline: var(--pds-spacing-button-gap-inline-s);
--button-background-color: var(--pds-color-button-primary-background-default);
--button-background-color-hover: var(
--pds-color-button-primary-background-hover
);
--button-background-color-active: var(
--pds-color-button-primary-background-active
);
--button-foreground-color: var(--pds-color-button-primary-foreground);
--button-foreground-color-hover: var(--button-foreground-color);
--button-border-color: var(--pds-color-button-primary-background-default);
--button-border-color-hover: var(--pds-color-button-primary-background-hover);
--button-border-color-active: var(--button-background-color-active);
--button-box-shadow-active: var(--pds-elevation-button-primary-active);
align-items: center;
background-color: var(--button-background-color);
border-color: var(--button-border-color);
border-radius: var(--pds-border-radius-button);
border-style: solid;
border-width: var(--pds-border-width-default);
color: var(--button-foreground-color);
cursor: pointer;
display: inline-flex;
font-family: var(--pds-typography-ff-default);
font-size: var(--button-font-size);
font-weight: var(--pds-typography-fw-medium);
gap: var(--button-gap-inline);
height: var(--button-height);
justify-content: center;
line-height: 1;
max-height: var(--button-height);
padding-block: 0;
padding-inline: var(--button-padding-inline);
text-decoration: none;
transition: var(--pds-animation-transition-button);
white-space: nowrap;
}
/* A working-indicator as the first child sits slightly looser than an icon
would at the same spot — tighten the leading edge and open up the gap to
the label so it doesn't read as crowding it. */
.pds-button:has(> .pds-button__leading > .pds-button__working-indicator) {
padding-inline-start: calc(
var(--button-padding-inline) - var(--pds-spacing-4xs)
);
}
.pds-button__working-indicator {
margin-inline-end: var(--pds-spacing-4xs);
}
/* Holds the working spinner and leading icon/status dot without adding a box,
so its children are still direct flex items of the button. */
.pds-button__leading {
display: contents;
}
.pds-button:disabled {
cursor: not-allowed;
opacity: 40%;
}
.pds-button:hover:enabled,
a.pds-button:hover {
--button-background-color: var(--button-background-color-hover);
--button-border-color: var(--button-border-color-hover);
color: var(--button-foreground-color-hover);
text-decoration: none;
}
.pds-button:active:enabled,
a.pds-button:active {
--button-background-color: var(--button-background-color-active);
--button-border-color: var(--button-border-color-active);
box-shadow: var(--button-box-shadow-active);
color: var(--button-foreground-color-hover);
text-decoration: none;
}
.pds-button:focus-visible {
outline: var(--pds-border-width-outline) solid
var(--pds-color-interactive-focus);
outline-offset: 1px;
transition: var(--pds-animation-transition-focus);
}
.pds-button__icon {
flex-shrink: 0;
}
.pds-button--l .pds-button__icon:not(.pds-status-dot) {
height: var(--button-font-size);
width: var(--button-font-size);
}
.pds-button__tally {
flex-shrink: 0;
}
/* Tally ring — lifts badge off filled button backgrounds */
.pds-button--primary .pds-button__tally,
.pds-button--highlight .pds-button__tally {
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.75);
}
/* Status dot — scale with button size */
.pds-button--xs .pds-status-dot {
--dot-size: 8px;
}
.pds-button--s .pds-status-dot {
--dot-size: 10px;
}
.pds-button--m .pds-status-dot {
--dot-size: 12px;
margin-inline-end: var(--pds-spacing-5xs);
}
.pds-button--l .pds-status-dot {
--dot-size: 14px;
margin-inline-end: var(--pds-spacing-4xs);
}
/* Full Width Button */
.pds-button--full-width {
width: 100%;
}
/* Button Sizes */
.pds-button--xs {
--button-font-size: var(--pds-typography-size-xs);
--button-height: var(--pds-spacing-button-height-xs);
--button-padding-inline: var(--pds-spacing-button-padding-inline-xs);
--button-gap-inline: var(--pds-spacing-button-gap-inline-xs);
}
.pds-button--s {
--button-font-size: var(--pds-typography-size-s);
--button-height: var(--pds-spacing-button-height-s);
--button-padding-inline: var(--pds-spacing-button-padding-inline-s);
-button-gap-inline: var(--pds-spacing-button-gap-inline-s);
}
.pds-button--m {
--button-font-size: var(--pds-typography-size-m);
--button-height: var(--pds-spacing-button-height-m);
--button-padding-inline: var(--pds-spacing-button-padding-inline-m);
--button-gap-inline: var(--pds-spacing-button-gap-inline-m);
}
.pds-button--l {
--button-font-size: var(--pds-typography-size-l);
--button-height: var(--pds-spacing-button-height-l);
--button-padding-inline: var(--pds-spacing-button-padding-inline-l);
--button-gap-inline: var(--pds-spacing-button-gap-inline-l);
}
/* Button Variants */
.pds-button--secondary {
--button-background-color: var(
--pds-color-button-secondary-background-default
);
--button-background-color-hover: var(
--pds-color-button-secondary-background-hover
);
--button-background-color-active: var(
--pds-color-button-secondary-background-active
);
--button-box-shadow-active: var(--pds-elevation-button-secondary-active);
--button-foreground-color: var(
--pds-color-button-secondary-foreground-default
);
--button-foreground-color-hover: var(
--pds-color-button-secondary-foreground-hover
);
--button-border-color: var(--pds-color-button-secondary-border);
--button-border-color-hover: var(--pds-color-button-secondary-border);
--button-border-color-active: var(--pds-color-button-secondary-border);
}
.pds-button--subtle {
--button-background-color: var(--pds-color-button-subtle-background-default);
--button-background-color-hover: var(
--pds-color-button-subtle-background-hover
);
--button-background-color-active: var(
--pds-color-button-subtle-background-active
);
--button-box-shadow-active: var(--pds-elevation-button-subtle-active);
--button-foreground-color: var(--pds-color-button-subtle-foreground);
/*
* Literal `transparent`, not a reference to the background token.
* The background tokens are alpha overlays so subtle reads correctly
* on any surface color — but border-color paints as its own stroke
* on top of the background fill, so aliasing the same translucent
* value here would double-composite in the border ring and show up
* as a visible seam, even at matching nominal color. `transparent`
* composites as nothing regardless of what's underneath, so the
* border occupies its width (keeping sizing consistent with every
* other variant) without ever painting.
*/
--button-border-color: transparent;
--button-border-color-hover: transparent;
--button-border-color-active: transparent;
}
.pds-button--critical {
--button-background-color: var(
--pds-color-button-critical-background-default
);
--button-background-color-hover: var(
--pds-color-button-critical-background-hover
);
--button-background-color-active: var(
--pds-color-button-critical-background-active
);
--button-box-shadow-active: var(--pds-elevation-button-critical-active);
--button-foreground-color: var(--pds-color-button-critical-foreground);
--button-border-color: var(--pds-color-button-critical-background-default);
--button-border-color-hover: var(
--pds-color-button-critical-background-hover
);
--button-border-color-active: var(--button-background-color-active);
}
.pds-button--critical-secondary {
--button-background-color: var(
--pds-color-button-critical-secondary-background-default
);
--button-background-color-hover: var(
--pds-color-button-critical-secondary-background-hover
);
--button-background-color-active: var(
--pds-color-button-critical-secondary-background-active
);
--button-box-shadow-active: var(
--pds-elevation-button-critical-secondary-active
);
--button-foreground-color: var(
--pds-color-button-critical-secondary-foreground
);
--button-border-color: var(
--pds-color-button-critical-secondary-border-default
);
--button-border-color-hover: var(
--pds-color-button-critical-secondary-border-default
);
--button-border-color-active: var(
--pds-color-button-critical-secondary-border-default
);
}
.pds-button--brand {
--button-background-color: var(--pds-color-button-brand-background-default);
--button-background-color-hover: var(
--pds-color-button-brand-background-hover
);
--button-background-color-active: var(
--pds-color-button-brand-background-active
);
--button-box-shadow-active: var(--pds-elevation-button-brand-active);
--button-foreground-color: var(--pds-color-button-brand-foreground);
--button-border-color: var(--pds-color-button-brand-background-default);
--button-border-color-hover: var(--pds-color-button-brand-background-hover);
--button-border-color-active: var(--button-background-color-active);
}
.pds-button--highlight {
--button-background-color: var(
--pds-color-button-highlight-background-default
);
--button-background-color-hover: var(
--pds-color-button-highlight-background-hover
);
--button-background-color-active: var(
--pds-color-button-highlight-background-active
);
--button-box-shadow-active: var(--pds-elevation-button-highlight-active);
--button-foreground-color: var(--pds-color-button-highlight-foreground);
--button-border-color: var(--pds-color-button-highlight-background-default);
--button-border-color-hover: var(
--pds-color-button-highlight-background-hover
);
--button-border-color-active: var(--button-background-color-active);
}
.pds-button--reverse {
--button-background-color: var(--pds-color-button-reverse-background-default);
--button-background-color-hover: var(
--pds-color-button-reverse-background-hover
);
--button-background-color-active: var(
--pds-color-button-reverse-background-active
);
--button-box-shadow-active: var(--pds-elevation-button-reverse-active);
--button-foreground-color: var(--pds-color-button-reverse-foreground);
--button-border-color: var(--pds-color-button-reverse-background-default);
--button-border-color-hover: var(--pds-color-button-reverse-background-hover);
--button-border-color-active: var(--button-background-color-active);
}
.pds-button--reverse-secondary {
--button-background-color: var(
--pds-color-button-reverse-secondary-background-default
);
--button-background-color-hover: var(
--pds-color-button-reverse-secondary-background-hover
);
--button-background-color-active: var(
--pds-color-button-reverse-secondary-background-active
);
--button-box-shadow-active: var(
--pds-elevation-button-reverse-secondary-active
);
--button-foreground-color: var(
--pds-color-button-reverse-secondary-foreground
);
--button-border-color: var(--pds-color-button-reverse-secondary-border);
--button-border-color-hover: var(--pds-color-button-reverse-secondary-border);
--button-border-color-active: var(
--pds-color-button-reverse-secondary-border
);
}
/* Agentic modifier (primary and secondary): the AI gradient as the border.
--pds-gradient-ai-active is a gradient, not a color, so it cannot go in
border-color. The border is transparent and the gradient is painted on the
border-box underneath; the fill layers sit on the padding-box above it. The
secondary background tokens are translucent, so an opaque surface layer
keeps the gradient from showing through the middle of the button. */
.pds-button--agentic {
--button-border-color: transparent;
--button-border-color-hover: transparent;
--button-border-color-active: transparent;
background-clip: padding-box, padding-box, border-box;
background-color: transparent;
background-image:
linear-gradient(
var(--button-background-color),
var(--button-background-color)
),
linear-gradient(
var(--pds-color-surface-default),
var(--pds-color-surface-default)
),
var(--pds-gradient-ai-active);
background-origin: border-box;
}
/* Special Variants */
.pds-button--link {
background-color: transparent;
border-color: transparent;
border: none;
border-radius: 0;
color: var(--pds-color-interactive-link-default);
height: unset;
justify-content: unset;
letter-spacing: 0;
max-height: unset;
padding-inline: 0;
padding: 0;
transition: var(--pds-animation-transition-button);
}
.pds-button--link:hover:enabled,
a.pds-button--link:hover {
color: var(--pds-color-interactive-link-hover);
text-decoration: underline;
}
.pds-button--link:active:enabled,
a.pds-button--link:active {
box-shadow: none;
color: var(--pds-color-interactive-link-active);
}
/* The link variant has no button container (background/border/padding) to
visually balance the icon against, unlike every other variant -- a
full icon-size step down reads as too small, so nudge it down with a
scale instead. Covers both Button's own icon and ButtonLink's cloned one. */
.pds-button--link .pds-button__icon,
.pds-button--link .pds-button-link__icon {
transform: scale(0.9);
}
/* Loading State */
.pds-button--loading {
cursor: not-allowed;
&:hover:enabled,
&:active {
--button-background-color: var(--pulse-regular-base-color);
--button-border-color: var(--pulse-regular-base-color);
}
}
.pds-button--loading.pds-pulse {
background-color: var(--pulse-regular-base-color);
background-image: none;
border-color: var(--pulse-regular-base-color);
color: transparent;
}
.pds-button--loading .pds-button__icon,
.pds-button--loading .pds-button__tally,
.pds-button--loading .pds-button__working-indicator {
opacity: 0;
}
/* Button Group */
.pds-button-group {
display: flex;
flex-direction: column;
row-gap: var(--pds-spacing-m);
width: 100%;
}
/* Opts out of the default mobile stack-to-full-width behavior above, for
groups used as a compact toolbar (e.g. next to a heading) rather than a
set of stacked primary/secondary actions. Wraps instead of overflowing
when there isn't room to stay on one line (e.g. a split button's menu
alongside another action at narrow widths). */
.pds-button-group--inline {
column-gap: var(--pds-spacing-s);
flex-direction: row;
flex-wrap: wrap;
row-gap: var(--pds-spacing-s);
width: unset;
}
/* --pds-bp-m. This repo has no postcss custom-media plugin, so the literal
value is used here; the port restores `(--pds-bp-m)`. */
@media (min-width: 768px) {
.pds-button-group {
column-gap: var(--pds-spacing-s);
flex-direction: row;
width: unset;
}
.pds-button-group--l {
column-gap: var(--pds-spacing-m);
}
}
Button
Starting point for a Button proposal.
Extends a PDS componentOpen in Storybook