Add the component to your project and install missing dependencies if needed.
import { css, cx } from "@linaria/core";
import type React from "react";
interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: "primary" | "secondary" | "danger" | "naked";
isLoading?: boolean;
}
export default function Button({
variant = "primary",
children,
className,
disabled,
isLoading,
...props
}: Props) {
const loaderClass = cx(
loaderStyle,
variant === "primary" && primaryLoaderStyle,
variant === "secondary" && secondaryLoaderStyle,
variant === "danger" && dangerLoaderStyle,
variant === "naked" && nakedLoaderStyle,
);
return (
<button
className={cx(
baseButtonStyle,
loadingStyle,
variant === "primary" && primaryStyle,
variant === "secondary" && secondaryStyle,
variant === "danger" && dangerStyle,
variant === "naked" && nakedStyle,
disabled && disabledStyle,
disabled && variant === "primary" && primaryDisabledStyle,
disabled && variant === "secondary" && secondaryDisabledStyle,
disabled && variant === "danger" && dangerDisabledStyle,
disabled && variant === "naked" && nakedDisabledStyle,
className,
)}
data-loading={isLoading}
disabled={disabled || isLoading}
{...props}
>
{children}
{isLoading && (
<div className={loadingOverlayStyle}>
<div className={loaderClass} />
</div>
)}
</button>
);
}
const baseButtonStyle = css`
@layer debase {
position: relative;
display: flex;
height: fit-content;
align-items: center;
justify-content: center;
border: none;
white-space: nowrap;
cursor: pointer;
gap: var(--debase__spacing__x05);
border-radius: var(--debase__radius);
font-size: var(--debase__font-size__normal);
font-family: var(--font-family-default);
padding: var(--debase__spacing__x1) var(--debase__spacing__x2);
width: fit-content;
}
`;
const loadingStyle = css`
@layer debase {
&[data-loading="true"] {
user-select: none;
color: transparent;
}
}
`;
const loaderStyle = css`
@layer debase {
width: 20px;
aspect-ratio: 1;
border-radius: 50%;
animation: l13 1s infinite linear;
@keyframes l13 {
100% { transform: rotate(1turn); }
}
}
`;
const loadingOverlayStyle = css`
@layer debase {
position: absolute;
top: 0;
left: 0;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
border-radius: var(--debase__radius);
display: none;
[data-loading="true"] & {
display: flex;
}
}
`;
const primaryStyle = css`
@layer debase {
color: var(--debase__color__primary-label);
background: linear-gradient(180deg, rgb(255 255 255 / 16%) 0%, rgb(255 255 255 / 0%) 100%), var(--debase__color__primary);
box-shadow: 0 0 0 1px var(--debase__color__primary), 0 1px 1px 0 rgb(9 9 11 / 15%), 0 0.75px 0 0 rgb(255 255 255 / 50%) inset;
&:hover:not([data-loading="true"]):not(:disabled) {
background: var(--hoverEffect), linear-gradient(180deg, rgb(255 255 255 / 16%) 0%, rgb(255 255 255 / 0%) 100%), var(--debase__color__primary);
}
}
`;
const primaryLoaderStyle = css`
@layer debase {
background: radial-gradient(farthest-side,var(--debase__color__primary-label) 94%,#0000) top/4px 4px no-repeat,
conic-gradient(#0000 30%,var(--debase__color__primary-label));
mask: radial-gradient(farthest-side, #0000 calc(100% - 4px), #000 0);
}
`;
const secondaryStyle = css`
@layer debase {
color: var(--debase__color__text);
outline: 1px solid var(--debase__color__line);
background: linear-gradient(180deg, rgb(9 9 11 / 0%) 0%, rgb(9 9 11 / 3%) 100%), var(--debase__color__background);
&:hover:not([data-loading="true"]):not(:disabled) {
background: var(--hoverEffect), linear-gradient(180deg, rgb(9 9 11 / 0%) 0%, rgb(9 9 11 / 3%) 100%), var(--debase__color__background);
}
}
`;
const secondaryLoaderStyle = css`
@layer debase {
background: radial-gradient(farthest-side,var(--debase__color__text) 94%,#0000) top/4px 4px no-repeat,
conic-gradient(#0000 30%,var(--debase__color__text));
mask: radial-gradient(farthest-side, #0000 calc(100% - 4px), #000 0);
}
`;
const dangerStyle = css`
@layer debase {
color: var(--debase__color__danger-label);
background: linear-gradient(180deg, rgb(255 255 255 / 16%) 0%, rgb(255 255 255 / 0%) 100%), var(--debase__color__danger);
box-shadow: 0 0 0 1px var(--debase__color__danger), 0 1px 1px 0 rgb(9 9 11 / 15%), 0 0.75px 0 0 rgb(255 255 255 / 50%) inset;
&:hover:not([data-loading="true"]):not(:disabled) {
background: var(--hoverEffect), linear-gradient(180deg, rgb(255 255 255 / 16%) 0%, rgb(255 255 255 / 0%) 100%), var(--debase__color__danger);
}
}
`;
const dangerLoaderStyle = css`
@layer debase {
background: radial-gradient(farthest-side,var(--debase__color__danger-label) 94%,#0000) top/4px 4px no-repeat,
conic-gradient(#0000 30%,var(--debase__color__danger-label));
mask: radial-gradient(farthest-side, #0000 calc(100% - 4px), #000 0);
}
`;
const nakedStyle = css`
@layer debase {
color: var(--debase__color__text);
background: transparent;
&:hover:not([data-loading="true"]):not(:disabled) {
background: var(--hoverEffect);
}
}
`;
const nakedLoaderStyle = css`
@layer debase {
background: radial-gradient(farthest-side,var(--debase__color__text) 94%,#0000) top/4px 4px no-repeat,
conic-gradient(#0000 30%,var(--debase__color__text));
mask: radial-gradient(farthest-side, #0000 calc(100% - 4px), #000 0);
}
`;
const disabledStyle = css`
@layer debase {
cursor: not-allowed;
}
`;
const primaryDisabledStyle = css`
@layer debase {
color: color-mix(in srgb, var(--debase__color__primary-label) 60%, gray);
background: color-mix(in srgb, var(--debase__color__primary) 60%, gray);
filter: contrast(0.8) brightness(0.9) saturate(0.7);
box-shadow: none;
}
`;
const secondaryDisabledStyle = css`
@layer debase {
color: color-mix(in srgb, var(--debase__color__text) 60%, gray);
outline: 1px solid color-mix(in srgb, var(--debase__color__line) 50%, gray);
background: color-mix(in srgb, var(--debase__color__background) 70%, gray);
filter: contrast(0.8) brightness(0.9) saturate(0.7);
}
`;
const dangerDisabledStyle = css`
@layer debase {
color: color-mix(in srgb, var(--debase__color__danger-label) 60%, gray);
background: color-mix(in srgb, var(--debase__color__danger) 60%, gray);
filter: contrast(0.8) brightness(0.9) saturate(0.7);
box-shadow: none;
}
`;
const nakedDisabledStyle = css`
@layer debase {
color: color-mix(in srgb, var(--debase__color__text) 60%, gray);
background: transparent;
filter: contrast(0.8) brightness(0.9) saturate(0.7);
}
`;The Button component offers four distinct variants to fit different UI needs:
Multiple variants of the buttons:
With a loading state:
The loading state maintains the original width of the button to prevent layout shifts when transitioning between normal and loading states. This ensures a smooth UI experience, especially in forms or button groups where sudden width changes could disrupt the layout.
With a disabled state: