← All proposals

Icon Button

Starting point for an IconButton proposal.

Extends a PDS componentOpen in Storybook

proposals/IconButton/icon-button.css229 lines · View on GitHub
.pds-icon-button {
	--icon-button-size: var(--pds-spacing-button-height-s);
	--icon-button-background: var(
		--pds-color-button-secondary-background-default
	);
	--icon-button-background-hover: var(
		--pds-color-button-secondary-background-hover
	);
	--icon-button-border: var(--pds-color-button-secondary-border);
	--icon-button-border-hover: var(--pds-color-button-secondary-border);
	--icon-button-foreground: var(
		--pds-color-button-secondary-foreground-default
	);

	align-items: center;
	background-color: var(--icon-button-background);
	border: var(--pds-border-width-default) solid transparent;
	border-radius: var(--pds-border-radius-input);
	box-sizing: border-box;
	color: var(--icon-button-foreground);
	cursor: pointer;
	display: flex;
	height: var(--icon-button-size);
	justify-content: center;
	position: relative;
	text-decoration: none;
	transition: var(--pds-animation-transition-button);
	width: var(--icon-button-size);
}

.pds-icon-button__content {
	align-content: stretch;
	align-items: center;
	display: flex;
	height: 100%;
	justify-content: center;
}

.pds-icon-button:disabled {
	cursor: not-allowed;
	opacity: 70%;
}

.pds-icon-button:disabled .pds-icon-button__content {
	opacity: 30%;
}

.pds-icon-button:hover:enabled,
a.pds-icon-button:hover {
	background-color: var(--icon-button-background-hover);
	color: var(--icon-button-foreground);
	text-decoration: none;
}

.pds-icon-button--has-border {
	border-color: var(--icon-button-border);
}

.pds-icon-button--has-border:hover:enabled,
a.pds-icon-button--has-border:hover {
	border-color: var(--icon-button-border-hover);
}

.pds-icon-button:focus-visible {
	outline: var(--pds-border-width-outline) solid
		var(--pds-color-interactive-focus);
}

/* Button Variants */
.pds-icon-button--reverse {
	--icon-button-background: var(
		--pds-color-button-reverse-secondary-background-default
	);
	--icon-button-background-hover: var(
		--pds-color-button-reverse-secondary-background-hover
	);
	--icon-button-border: var(--pds-color-button-reverse-secondary-border);
	--icon-button-border-hover: var(--pds-color-button-reverse-secondary-border);
	--icon-button-foreground: var(
		--pds-color-button-reverse-secondary-foreground
	);

	&:focus-visible {
		outline-color: var(--pds-color-interactive-reverse-focus);
	}
}

.pds-icon-button--critical {
	--icon-button-background: var(
		--pds-color-button-critical-secondary-background-default
	);
	--icon-button-background-hover: var(
		--pds-color-button-critical-secondary-background-hover
	);
	--icon-button-border: var(
		--pds-color-button-critical-secondary-border-default
	);
	--icon-button-border-hover: var(
		--pds-color-button-critical-secondary-border-hover
	);
	--icon-button-foreground: var(
		--pds-color-button-critical-secondary-foreground
	);
}

/* Pressed / active toggle state — fill with border color, subtle inner ring for edge definition */
.pds-icon-button--pressed:not(.pds-icon-button--critical),
.pds-icon-button--pressed:not(.pds-icon-button--critical):hover:enabled {
	background-color: var(--icon-button-border);
	border-color: var(--icon-button-border);
	box-shadow: var(--pds-elevation-icon-button-pressed);
}

/* Active (click) state */
.pds-icon-button:active:enabled,
a.pds-icon-button:active {
	background-color: var(--pds-color-button-secondary-background-active);
	box-shadow: var(--pds-elevation-button-secondary-active);
}

.pds-icon-button--critical:active:enabled,
a.pds-icon-button--critical:active {
	background-color: var(
		--pds-color-button-critical-secondary-background-active
	);
	box-shadow: var(--pds-elevation-button-critical-secondary-active);
}

.pds-icon-button--reverse:active:enabled,
a.pds-icon-button--reverse:active {
	background-color: var(--pds-color-button-reverse-secondary-background-active);
	box-shadow: var(--pds-elevation-button-reverse-secondary-active);
}

/* Agentic: 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. The fill is a custom property so hover, active
   and pressed can swap it. Selectors are doubled up to beat the has-border,
   pressed and hover rules above. */
.pds-icon-button.pds-icon-button--agentic {
	--icon-button-fill: var(--icon-button-background);

	background-clip: padding-box, padding-box, border-box;
	background-color: transparent;
	background-image:
		linear-gradient(var(--icon-button-fill), var(--icon-button-fill)),
		linear-gradient(
			var(--pds-color-surface-default),
			var(--pds-color-surface-default)
		),
		var(--pds-gradient-ai-active);
	background-origin: border-box;
	border-color: transparent;
}

.pds-icon-button.pds-icon-button--agentic:hover:enabled {
	--icon-button-fill: var(--icon-button-background-hover);

	background-color: transparent;
	border-color: transparent;
}

.pds-icon-button.pds-icon-button--agentic:active:enabled,
.pds-icon-button.pds-icon-button--agentic.pds-icon-button--pressed,
.pds-icon-button.pds-icon-button--agentic.pds-icon-button--pressed:hover:enabled {
	--icon-button-fill: var(--pds-color-button-secondary-background-active);

	background-color: transparent;
	border-color: transparent;
}

/* Button Sizes */
.pds-icon-button--xs {
	--icon-button-size: var(--pds-spacing-button-height-xs);
}

.pds-icon-button--m {
	--icon-button-size: var(--pds-spacing-button-height-m);
}

/* Animation & Icons */
.pds-icon-button__icon-wrapper {
	align-items: center;
	display: flex;
	inset: 0;
	justify-content: center;
	position: absolute;
}

.pds-icon-button--fadeOut {
	-webkit-animation: scale-down-center 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94)
		both;
	animation: scale-down-center 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
	opacity: 0;
	transition: 0.2s ease-in;
}

.pds-icon-button--hide {
	display: none;
}

.pds-icon-button--scaleIn {
	-webkit-animation: scale-in-center 0.2s
		cubic-bezier(0.175, 0.885, 0.32, 1.275) both;
	animation: scale-in-center 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275) both;
}

/** mods to specific icons **/

.pds-icon--copy.pds-icon--copy.pds-icon-button__icon {
	transform: translateX(8%) translateY(-2%);
}

@keyframes scale-in-center {
	0% {
		opacity: 1;
		-webkit-transform: scale(0);
		transform: scale(0);
	}
	100% {
		opacity: 1;
		-webkit-transform: scale(1);
		transform: scale(1);
	}
}