"use client";
import { cva, type VariantProps } from "class-variance-authority";
import { motion } from "motion/react";
import {
type ButtonHTMLAttributes,
forwardRef,
type KeyboardEvent,
type PointerEvent,
type ReactNode,
useCallback,
useEffect,
useLayoutEffect,
useState,
} from "react";
import { cn } from "@/lib/utils";
const componentThemeClassName =
"[--ic-background:#ffffff] [--ic-foreground:#111111] [--ic-primary:#111111] [--ic-secondary:#646b75] [--ic-surface-border:#e9edf2] [--ic-border:#e3e7ec] [--ic-card:#ffffff] [--ic-card-foreground:#111111] [--ic-muted:#f5f7fa] [--ic-muted-foreground:#6d7480] [--ic-accent:#f3f5f8] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--ic-accent-foreground:#111111] [--ic-input:#e3e7ec] [--ic-ring:rgba(17,17,17,0.16)] [--ic-destructive:#dc2626] [--ic-paper:#fcfcfd] [--ic-popover-foreground:#111111] [--ic-brand:#0ea5e9] [--ic-brand-soft:#bae6fd] [--ic-shadow-soft:0_18px_38px_-24px_rgba(15,23,42,0.35)] [--ic-chart-1:oklch(0.52_0.19_254)] [--ic-chart-2:oklch(0.74_0.11_232)] [--ic-chart-3:oklch(0.42_0.16_262)] [--ic-chart-4:oklch(0.84_0.07_228)] [--ic-chart-5:oklch(0.62_0.14_240)] [--color-background:var(--ic-background)] [--color-foreground:var(--ic-foreground)] [--color-primary:var(--ic-primary)] [--color-secondary:var(--ic-secondary)] [--color-border:var(--ic-border)] [--color-card:var(--ic-card)] [--color-card-foreground:var(--ic-card-foreground)] [--color-muted:var(--ic-muted)] [--color-muted-foreground:var(--ic-muted-foreground)] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--color-input:var(--ic-input)] [--color-ring:var(--ic-ring)] [--color-destructive:var(--ic-destructive)] [--color-paper:var(--ic-paper)] [--color-popover-foreground:var(--ic-popover-foreground)] [--color-brand:var(--ic-brand)] [--color-brand-soft:var(--ic-brand-soft)] [--color-chart-1:var(--ic-chart-1)] [--color-chart-2:var(--ic-chart-2)] [--color-chart-3:var(--ic-chart-3)] [--color-chart-4:var(--ic-chart-4)] [--color-chart-5:var(--ic-chart-5)] dark:[--ic-background:#111111] dark:[--ic-foreground:#f6f3ec] dark:[--ic-primary:#f6f3ec] dark:[--ic-secondary:#cbc6bb] dark:[--ic-surface-border:#2a2a25] dark:[--ic-border:#2b2a25] dark:[--ic-card:#111111] dark:[--ic-card-foreground:#f6f3ec] dark:[--ic-muted:#171716] dark:[--ic-muted-foreground:#9a958a] dark:[--ic-accent:#1a1a18] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] dark:[--ic-accent-foreground:#f6f3ec] dark:[--ic-input:#2b2a25] dark:[--ic-ring:rgba(246,243,236,0.18)] dark:[--ic-destructive:#f87171] dark:[--ic-paper:#171716] dark:[--ic-popover-foreground:#f6f3ec] dark:[--ic-brand:#38bdf8] dark:[--ic-brand-soft:#0c4a6e] dark:[--ic-shadow-soft:0_20px_44px_-28px_rgba(0,0,0,0.6)] dark:[--ic-chart-1:oklch(0.68_0.17_250)] dark:[--ic-chart-2:oklch(0.82_0.09_225)] dark:[--ic-chart-3:oklch(0.58_0.15_260)] dark:[--ic-chart-4:oklch(0.75_0.12_235)] dark:[--ic-chart-5:oklch(0.88_0.06_220)]";
const buttonVariantStyles = {
default: "bg-primary text-background hover:bg-primary/90",
destructive: "bg-red-600 text-white hover:bg-red-600/90",
outline:
"border border-input bg-background text-foreground hover:bg-accent/60 hover:text-accent-foreground",
secondary:
"bg-neutral-200 text-neutral-900 hover:bg-neutral-300 dark:bg-neutral-600 dark:text-neutral-50 dark:hover:bg-neutral-500",
ghost: "text-foreground hover:bg-accent/60 hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
} as const;
const buttonSizeStyles = {
sm: "min-h-11 px-3.5 text-xs",
md: "min-h-11 px-5 text-sm",
lg: "min-h-12 px-8 text-base",
unstyled: "min-h-11 min-w-11 px-0 py-0 text-sm",
} as const;
const buttonVariants = cva(
"relative inline-flex cursor-pointer touch-manipulation select-none items-center justify-center overflow-hidden rounded-lg font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: buttonVariantStyles,
size: buttonSizeStyles,
},
defaultVariants: {
variant: "default",
size: "md",
},
}
);
const buttonRootVariants = cva(
"relative inline-flex cursor-pointer touch-manipulation select-none items-center justify-center overflow-hidden rounded-lg font-medium transition-[background-color,color,filter] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[pressed=true]:brightness-95",
{
variants: {
variant: buttonVariantStyles,
},
defaultVariants: {
variant: "default",
},
}
);
const buttonContentVariants = cva(
"relative z-10 inline-flex items-center justify-center gap-2 whitespace-nowrap text-inherit [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
size: buttonSizeStyles,
},
defaultVariants: {
size: "md",
},
}
);
type Ripple = { id: string; x: number; y: number; size: number };
/**
* Prop names that exist on both DOM `button` and `motion.button` but with incompatible types
* (Motion uses them for gestures / layout animation, not DOM events).
*/
type ButtonHTMLAttributesForMotion = Omit<
ButtonHTMLAttributes<HTMLButtonElement>,
| "onAnimationEnd"
| "onAnimationIteration"
| "onAnimationStart"
| "onDrag"
| "onDragEnd"
| "onDragEnter"
| "onDragExit"
| "onDragLeave"
| "onDragOver"
| "onDragStart"
| "onDrop"
>;
function useMeasure<T extends HTMLElement = HTMLElement>(): [
(node: T | null) => void,
{ width: number; height: number },
] {
const [element, setElement] = useState<T | null>(null);
const [bounds, setBounds] = useState({ width: 0, height: 0 });
const ref = useCallback((node: T | null) => {
setElement(node);
}, []);
useEffect(() => {
if (!(element && typeof ResizeObserver !== "undefined")) {
return;
}
const observer = new ResizeObserver(([entry]) => {
setBounds((current) => {
const next = {
width: entry.contentRect.width,
height: entry.contentRect.height,
};
if (current.width === next.width && current.height === next.height) {
return current;
}
return next;
});
});
observer.observe(element);
return () => observer.disconnect();
}, [element]);
return [ref, bounds];
}
export type ButtonProps = ButtonHTMLAttributesForMotion &
VariantProps<typeof buttonVariants> & {
animateSize?: boolean;
children?: ReactNode;
disableRipple?: boolean;
icon?: ReactNode;
iconPosition?: "start" | "end";
};
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
(
{
animateSize = false,
children,
className,
disabled,
disableRipple = false,
icon,
iconPosition = "start",
onBlur,
onKeyDown,
onKeyUp,
onPointerCancel,
onPointerDown,
onPointerLeave,
onPointerUp,
size,
style,
type = "button",
variant,
...props
},
ref
) => {
const [buttonNode, setButtonNode] = useState<HTMLButtonElement | null>(
null
);
const [contentRef, bounds] = useMeasure<HTMLSpanElement>();
const [borderWidth, setBorderWidth] = useState(0);
const [isPressed, setIsPressed] = useState(false);
const [ripples, setRipples] = useState<Ripple[]>([]);
const canAnimate = !disabled;
const allowsRipple = !disableRipple && variant !== "link" && canAnimate;
const shouldAnimateSize = animateSize && style?.width == null;
const animatedWidth =
shouldAnimateSize && bounds.width > 0
? Math.ceil(bounds.width + borderWidth)
: undefined;
const setButtonRef = useCallback(
(node: HTMLButtonElement | null) => {
setButtonNode(node);
if (typeof ref === "function") {
ref(node);
return;
}
if (ref) {
ref.current = node;
}
},
[ref]
);
useLayoutEffect(() => {
if (!(buttonNode && animateSize)) {
return;
}
const styles = window.getComputedStyle(buttonNode);
const next =
(Number.parseFloat(styles.borderLeftWidth) || 0) +
(Number.parseFloat(styles.borderRightWidth) || 0);
setBorderWidth((current) => (current === next ? current : next));
});
useEffect(() => {
if (disabled) {
setIsPressed(false);
}
}, [disabled]);
const handlePointerDown = (e: PointerEvent<HTMLButtonElement>) => {
onPointerDown?.(e);
if (e.defaultPrevented || disabled) {
return;
}
setIsPressed(true);
if (!(allowsRipple && e.button === 0)) {
return;
}
const rect = e.currentTarget.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const rippleSize =
2 *
Math.max(
Math.hypot(x, y),
Math.hypot(rect.width - x, y),
Math.hypot(x, rect.height - y),
Math.hypot(rect.width - x, rect.height - y)
);
const id =
typeof crypto !== "undefined" && crypto.randomUUID
? crypto.randomUUID()
: `${Date.now()}-${Math.random()}`;
setRipples((current) => [...current, { id, x, y, size: rippleSize }]);
};
const handlePointerUp = (e: PointerEvent<HTMLButtonElement>) => {
onPointerUp?.(e);
setIsPressed(false);
};
const handlePointerLeave = (e: PointerEvent<HTMLButtonElement>) => {
onPointerLeave?.(e);
setIsPressed(false);
};
const handlePointerCancel = (e: PointerEvent<HTMLButtonElement>) => {
onPointerCancel?.(e);
setIsPressed(false);
};
const handleKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {
onKeyDown?.(e);
if (
e.defaultPrevented ||
disabled ||
e.repeat ||
(e.key !== " " && e.key !== "Enter")
) {
return;
}
setIsPressed(true);
};
const handleKeyUp = (e: KeyboardEvent<HTMLButtonElement>) => {
onKeyUp?.(e);
if (e.key === " " || e.key === "Enter") {
setIsPressed(false);
}
};
const renderedIcon = icon ? (
<span aria-hidden className="inline-flex shrink-0 items-center">
{icon}
</span>
) : null;
return (
<motion.button
animate={
animatedWidth
? {
scale: canAnimate && isPressed ? 0.96 : 1,
width: animatedWidth,
}
: {
scale: canAnimate && isPressed ? 0.96 : 1,
}
}
className={cn(
componentThemeClassName,
buttonRootVariants({ variant }),
className
)}
data-pressed={isPressed ? "true" : "false"}
disabled={disabled}
initial={false}
onBlur={(e) => {
onBlur?.(e);
setIsPressed(false);
}}
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
onPointerCancel={handlePointerCancel}
onPointerDown={handlePointerDown}
onPointerLeave={handlePointerLeave}
onPointerUp={handlePointerUp}
ref={setButtonRef}
style={style}
transition={{
scale: { type: "spring", stiffness: 640, damping: 38, mass: 0.85 },
width: {
type: "spring",
stiffness: 420,
damping: 34,
mass: 0.9,
},
y: { type: "spring", stiffness: 340, damping: 28, mass: 0.8 },
}}
type={type}
whileHover={canAnimate && variant !== "link" ? { y: -1 } : undefined}
{...props}
>
<span
aria-hidden
className="pointer-events-none absolute inset-0 z-0 overflow-hidden rounded-[inherit]"
>
{ripples.map((ripple) => (
<motion.span
animate={{ opacity: 0, scale: 1 }}
className="pointer-events-none absolute -translate-x-1/2 -translate-y-1/2 rounded-full bg-current"
initial={{ opacity: 0.22, scale: 0 }}
key={ripple.id}
onAnimationComplete={() => {
setRipples((current) =>
current.filter((item) => item.id !== ripple.id)
);
}}
style={{
height: ripple.size,
left: ripple.x,
top: ripple.y,
width: ripple.size,
}}
transition={{
duration: 0.55,
ease: [0.22, 1, 0.36, 1],
}}
/>
))}
</span>
<span
className={buttonContentVariants({ size })}
ref={shouldAnimateSize ? contentRef : undefined}
>
{iconPosition === "start" ? renderedIcon : null}
{children}
{iconPosition === "end" ? renderedIcon : null}
</span>
</motion.button>
);
}
);
Button.displayName = "Button";
export { Button, buttonVariants };
edwinvakayil's button
Try the interaction, copy the React source files, or download the complete example.
About edwinvakayil's button
edwinvakayil's button is an animated React and Tailwind CSS button by Edwin Vakayil. Try the interaction, copy the React source, or download the example with creator credit.
- Effects and categories
- Hover effects, Animated
- Using this example
- Copy a TSX file or download the original source and standalone demo. Test keyboard focus, motion and contrast in your own project.
Usage, Tailwind and licensing questions
Read the source files
import-examples/button.tsx
import {Button} from "@/registry/button";export default function Demo(){return <div className="flex flex-wrap gap-3"><Button>Get started</Button><Button variant="outline">Outline</Button><Button variant="secondary">Secondary</Button></div>}registry/button.tsx
"use client";
import { cva, type VariantProps } from "class-variance-authority";
import { motion } from "motion/react";
import {
type ButtonHTMLAttributes,
forwardRef,
type KeyboardEvent,
type PointerEvent,
type ReactNode,
useCallback,
useEffect,
useLayoutEffect,
useState,
} from "react";
import { cn } from "@/lib/utils";
const componentThemeClassName =
"[--ic-background:#ffffff] [--ic-foreground:#111111] [--ic-primary:#111111] [--ic-secondary:#646b75] [--ic-surface-border:#e9edf2] [--ic-border:#e3e7ec] [--ic-card:#ffffff] [--ic-card-foreground:#111111] [--ic-muted:#f5f7fa] [--ic-muted-foreground:#6d7480] [--ic-accent:#f3f5f8] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--ic-accent-foreground:#111111] [--ic-input:#e3e7ec] [--ic-ring:rgba(17,17,17,0.16)] [--ic-destructive:#dc2626] [--ic-paper:#fcfcfd] [--ic-popover-foreground:#111111] [--ic-brand:#0ea5e9] [--ic-brand-soft:#bae6fd] [--ic-shadow-soft:0_18px_38px_-24px_rgba(15,23,42,0.35)] [--ic-chart-1:oklch(0.52_0.19_254)] [--ic-chart-2:oklch(0.74_0.11_232)] [--ic-chart-3:oklch(0.42_0.16_262)] [--ic-chart-4:oklch(0.84_0.07_228)] [--ic-chart-5:oklch(0.62_0.14_240)] [--color-background:var(--ic-background)] [--color-foreground:var(--ic-foreground)] [--color-primary:var(--ic-primary)] [--color-secondary:var(--ic-secondary)] [--color-border:var(--ic-border)] [--color-card:var(--ic-card)] [--color-card-foreground:var(--ic-card-foreground)] [--color-muted:var(--ic-muted)] [--color-muted-foreground:var(--ic-muted-foreground)] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--color-input:var(--ic-input)] [--color-ring:var(--ic-ring)] [--color-destructive:var(--ic-destructive)] [--color-paper:var(--ic-paper)] [--color-popover-foreground:var(--ic-popover-foreground)] [--color-brand:var(--ic-brand)] [--color-brand-soft:var(--ic-brand-soft)] [--color-chart-1:var(--ic-chart-1)] [--color-chart-2:var(--ic-chart-2)] [--color-chart-3:var(--ic-chart-3)] [--color-chart-4:var(--ic-chart-4)] [--color-chart-5:var(--ic-chart-5)] dark:[--ic-background:#111111] dark:[--ic-foreground:#f6f3ec] dark:[--ic-primary:#f6f3ec] dark:[--ic-secondary:#cbc6bb] dark:[--ic-surface-border:#2a2a25] dark:[--ic-border:#2b2a25] dark:[--ic-card:#111111] dark:[--ic-card-foreground:#f6f3ec] dark:[--ic-muted:#171716] dark:[--ic-muted-foreground:#9a958a] dark:[--ic-accent:#1a1a18] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] dark:[--ic-accent-foreground:#f6f3ec] dark:[--ic-input:#2b2a25] dark:[--ic-ring:rgba(246,243,236,0.18)] dark:[--ic-destructive:#f87171] dark:[--ic-paper:#171716] dark:[--ic-popover-foreground:#f6f3ec] dark:[--ic-brand:#38bdf8] dark:[--ic-brand-soft:#0c4a6e] dark:[--ic-shadow-soft:0_20px_44px_-28px_rgba(0,0,0,0.6)] dark:[--ic-chart-1:oklch(0.68_0.17_250)] dark:[--ic-chart-2:oklch(0.82_0.09_225)] dark:[--ic-chart-3:oklch(0.58_0.15_260)] dark:[--ic-chart-4:oklch(0.75_0.12_235)] dark:[--ic-chart-5:oklch(0.88_0.06_220)]";
const buttonVariantStyles = {
default: "bg-primary text-background hover:bg-primary/90",
destructive: "bg-red-600 text-white hover:bg-red-600/90",
outline:
"border border-input bg-background text-foreground hover:bg-accent/60 hover:text-accent-foreground",
secondary:
"bg-neutral-200 text-neutral-900 hover:bg-neutral-300 dark:bg-neutral-600 dark:text-neutral-50 dark:hover:bg-neutral-500",
ghost: "text-foreground hover:bg-accent/60 hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
} as const;
const buttonSizeStyles = {
sm: "min-h-11 px-3.5 text-xs",
md: "min-h-11 px-5 text-sm",
lg: "min-h-12 px-8 text-base",
unstyled: "min-h-11 min-w-11 px-0 py-0 text-sm",
} as const;
const buttonVariants = cva(
"relative inline-flex cursor-pointer touch-manipulation select-none items-center justify-center overflow-hidden rounded-lg font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: buttonVariantStyles,
size: buttonSizeStyles,
},
defaultVariants: {
variant: "default",
size: "md",
},
}
);
const buttonRootVariants = cva(
"relative inline-flex cursor-pointer touch-manipulation select-none items-center justify-center overflow-hidden rounded-lg font-medium transition-[background-color,color,filter] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[pressed=true]:brightness-95",
{
variants: {
variant: buttonVariantStyles,
},
defaultVariants: {
variant: "default",
},
}
);
const buttonContentVariants = cva(
"relative z-10 inline-flex items-center justify-center gap-2 whitespace-nowrap text-inherit [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
size: buttonSizeStyles,
},
defaultVariants: {
size: "md",
},
}
);
type Ripple = { id: string; x: number; y: number; size: number };
/**
* Prop names that exist on both DOM `button` and `motion.button` but with incompatible types
* (Motion uses them for gestures / layout animation, not DOM events).
*/
type ButtonHTMLAttributesForMotion = Omit<
ButtonHTMLAttributes<HTMLButtonElement>,
| "onAnimationEnd"
| "onAnimationIteration"
| "onAnimationStart"
| "onDrag"
| "onDragEnd"
| "onDragEnter"
| "onDragExit"
| "onDragLeave"
| "onDragOver"
| "onDragStart"
| "onDrop"
>;
function useMeasure<T extends HTMLElement = HTMLElement>(): [
(node: T | null) => void,
{ width: number; height: number },
] {
const [element, setElement] = useState<T | null>(null);
const [bounds, setBounds] = useState({ width: 0, height: 0 });
const ref = useCallback((node: T | null) => {
setElement(node);
}, []);
useEffect(() => {
if (!(element && typeof ResizeObserver !== "undefined")) {
return;
}
const observer = new ResizeObserver(([entry]) => {
setBounds((current) => {
const next = {
width: entry.contentRect.width,
height: entry.contentRect.height,
};
if (current.width === next.width && current.height === next.height) {
return current;
}
return next;
});
});
observer.observe(element);
return () => observer.disconnect();
}, [element]);
return [ref, bounds];
}
export type ButtonProps = ButtonHTMLAttributesForMotion &
VariantProps<typeof buttonVariants> & {
animateSize?: boolean;
children?: ReactNode;
disableRipple?: boolean;
icon?: ReactNode;
iconPosition?: "start" | "end";
};
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
(
{
animateSize = false,
children,
className,
disabled,
disableRipple = false,
icon,
iconPosition = "start",
onBlur,
onKeyDown,
onKeyUp,
onPointerCancel,
onPointerDown,
onPointerLeave,
onPointerUp,
size,
style,
type = "button",
variant,
...props
},
ref
) => {
const [buttonNode, setButtonNode] = useState<HTMLButtonElement | null>(
null
);
const [contentRef, bounds] = useMeasure<HTMLSpanElement>();
const [borderWidth, setBorderWidth] = useState(0);
const [isPressed, setIsPressed] = useState(false);
const [ripples, setRipples] = useState<Ripple[]>([]);
const canAnimate = !disabled;
const allowsRipple = !disableRipple && variant !== "link" && canAnimate;
const shouldAnimateSize = animateSize && style?.width == null;
const animatedWidth =
shouldAnimateSize && bounds.width > 0
? Math.ceil(bounds.width + borderWidth)
: undefined;
const setButtonRef = useCallback(
(node: HTMLButtonElement | null) => {
setButtonNode(node);
if (typeof ref === "function") {
ref(node);
return;
}
if (ref) {
ref.current = node;
}
},
[ref]
);
useLayoutEffect(() => {
if (!(buttonNode && animateSize)) {
return;
}
const styles = window.getComputedStyle(buttonNode);
const next =
(Number.parseFloat(styles.borderLeftWidth) || 0) +
(Number.parseFloat(styles.borderRightWidth) || 0);
setBorderWidth((current) => (current === next ? current : next));
});
useEffect(() => {
if (disabled) {
setIsPressed(false);
}
}, [disabled]);
const handlePointerDown = (e: PointerEvent<HTMLButtonElement>) => {
onPointerDown?.(e);
if (e.defaultPrevented || disabled) {
return;
}
setIsPressed(true);
if (!(allowsRipple && e.button === 0)) {
return;
}
const rect = e.currentTarget.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const rippleSize =
2 *
Math.max(
Math.hypot(x, y),
Math.hypot(rect.width - x, y),
Math.hypot(x, rect.height - y),
Math.hypot(rect.width - x, rect.height - y)
);
const id =
typeof crypto !== "undefined" && crypto.randomUUID
? crypto.randomUUID()
: `${Date.now()}-${Math.random()}`;
setRipples((current) => [...current, { id, x, y, size: rippleSize }]);
};
const handlePointerUp = (e: PointerEvent<HTMLButtonElement>) => {
onPointerUp?.(e);
setIsPressed(false);
};
const handlePointerLeave = (e: PointerEvent<HTMLButtonElement>) => {
onPointerLeave?.(e);
setIsPressed(false);
};
const handlePointerCancel = (e: PointerEvent<HTMLButtonElement>) => {
onPointerCancel?.(e);
setIsPressed(false);
};
const handleKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {
onKeyDown?.(e);
if (
e.defaultPrevented ||
disabled ||
e.repeat ||
(e.key !== " " && e.key !== "Enter")
) {
return;
}
setIsPressed(true);
};
const handleKeyUp = (e: KeyboardEvent<HTMLButtonElement>) => {
onKeyUp?.(e);
if (e.key === " " || e.key === "Enter") {
setIsPressed(false);
}
};
const renderedIcon = icon ? (
<span aria-hidden className="inline-flex shrink-0 items-center">
{icon}
</span>
) : null;
return (
<motion.button
animate={
animatedWidth
? {
scale: canAnimate && isPressed ? 0.96 : 1,
width: animatedWidth,
}
: {
scale: canAnimate && isPressed ? 0.96 : 1,
}
}
className={cn(
componentThemeClassName,
buttonRootVariants({ variant }),
className
)}
data-pressed={isPressed ? "true" : "false"}
disabled={disabled}
initial={false}
onBlur={(e) => {
onBlur?.(e);
setIsPressed(false);
}}
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
onPointerCancel={handlePointerCancel}
onPointerDown={handlePointerDown}
onPointerLeave={handlePointerLeave}
onPointerUp={handlePointerUp}
ref={setButtonRef}
style={style}
transition={{
scale: { type: "spring", stiffness: 640, damping: 38, mass: 0.85 },
width: {
type: "spring",
stiffness: 420,
damping: 34,
mass: 0.9,
},
y: { type: "spring", stiffness: 340, damping: 28, mass: 0.8 },
}}
type={type}
whileHover={canAnimate && variant !== "link" ? { y: -1 } : undefined}
{...props}
>
<span
aria-hidden
className="pointer-events-none absolute inset-0 z-0 overflow-hidden rounded-[inherit]"
>
{ripples.map((ripple) => (
<motion.span
animate={{ opacity: 0, scale: 1 }}
className="pointer-events-none absolute -translate-x-1/2 -translate-y-1/2 rounded-full bg-current"
initial={{ opacity: 0.22, scale: 0 }}
key={ripple.id}
onAnimationComplete={() => {
setRipples((current) =>
current.filter((item) => item.id !== ripple.id)
);
}}
style={{
height: ripple.size,
left: ripple.x,
top: ripple.y,
width: ripple.size,
}}
transition={{
duration: 0.55,
ease: [0.22, 1, 0.36, 1],
}}
/>
))}
</span>
<span
className={buttonContentVariants({ size })}
ref={shouldAnimateSize ? contentRef : undefined}
>
{iconPosition === "start" ? renderedIcon : null}
{children}
{iconPosition === "end" ? renderedIcon : null}
</span>
</motion.button>
);
}
);
Button.displayName = "Button";
export { Button, buttonVariants };
lib/utils.ts
import type { ClassValue } from "clsx";
import { clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
demo.tsx
export { default as default } from "@/import-examples/button";CSS
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
@layer properties;
@layer theme, base, components, utilities;
@layer theme {
:root, :host {
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
"Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol", "Noto Color Emoji";
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
"Courier New", monospace;
--color-red-600: oklch(57.7% 0.245 27.325);
--color-neutral-50: oklch(98.5% 0 none);
--color-neutral-200: oklch(92.2% 0 none);
--color-neutral-300: oklch(87% 0 none);
--color-neutral-500: oklch(55.6% 0 none);
--color-neutral-600: oklch(43.9% 0 none);
--color-neutral-900: oklch(20.5% 0 none);
--color-white: #fff;
--spacing: 0.25rem;
--text-xs: 0.75rem;
--text-xs--line-height: calc(1 / 0.75);
--text-sm: 0.875rem;
--text-sm--line-height: calc(1.25 / 0.875);
--text-base: 1rem;
--text-base--line-height: calc(1.5 / 1);
--font-weight-medium: 500;
--default-transition-duration: 150ms;
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
--default-font-family: var(--font-sans);
--default-mono-font-family: var(--font-mono);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-primary: var(--primary);
--color-secondary: var(--secondary);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-destructive: var(--destructive);
--color-popover-foreground: var(--popover-foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
}
}
@layer base {
*, ::after, ::before, ::backdrop, ::file-selector-button {
box-sizing: border-box;
margin: 0;
padding: 0;
border: 0 solid;
}
html, :host {
line-height: 1.5;
-webkit-text-size-adjust: 100%;
tab-size: 4;
font-family: var(--default-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");
font-feature-settings: var(--default-font-feature-settings, normal);
font-variation-settings: var(--default-font-variation-settings, normal);
-webkit-tap-highlight-color: transparent;
}
hr {
height: 0;
color: inherit;
border-top-width: 1px;
}
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
h1, h2, h3, h4, h5, h6 {
font-size: inherit;
font-weight: inherit;
}
a {
color: inherit;
-webkit-text-decoration: inherit;
text-decoration: inherit;
}
b, strong {
font-weight: bolder;
}
code, kbd, samp, pre {
font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);
font-feature-settings: var(--default-mono-font-feature-settings, normal);
font-variation-settings: var(--default-mono-font-variation-settings, normal);
font-size: 1em;
}
small {
font-size: 80%;
}
sub, sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
table {
text-indent: 0;
border-color: inherit;
border-collapse: collapse;
}
:-moz-focusring:where(:not(iframe)) {
outline: auto;
}
progress {
vertical-align: baseline;
}
summary {
display: list-item;
}
ol, ul, menu {
list-style: none;
}
img, svg, video, canvas, audio, iframe, embed, object {
display: block;
vertical-align: middle;
}
img, video {
max-width: 100%;
height: auto;
}
button, input, select, optgroup, textarea, ::file-selector-button {
font: inherit;
font-feature-settings: inherit;
font-variation-settings: inherit;
letter-spacing: inherit;
color: inherit;
border-radius: 0;
background-color: transparent;
opacity: 1;
}
:where(select:is([multiple], [size])) optgroup {
font-weight: bolder;
}
:where(select:is([multiple], [size])) optgroup option {
padding-inline-start: 20px;
}
::file-selector-button {
margin-inline-end: 4px;
}
::placeholder {
opacity: 1;
}
@supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {
::placeholder {
color: currentcolor;
@supports (color: color-mix(in lab, red, red)) {
color: color-mix(in oklab, currentcolor 50%, transparent);
}
}
}
textarea {
resize: vertical;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-date-and-time-value {
min-height: 1lh;
text-align: inherit;
}
::-webkit-datetime-edit {
display: inline-flex;
}
::-webkit-datetime-edit-fields-wrapper {
padding: 0;
}
::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {
padding-block: 0;
}
::-webkit-calendar-picker-indicator {
line-height: 1;
}
:-moz-ui-invalid {
box-shadow: none;
}
button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button {
appearance: button;
}
::-webkit-inner-spin-button, ::-webkit-outer-spin-button {
height: auto;
}
[hidden]:where(:not([hidden="until-found"])) {
display: none !important;
}
}
@layer utilities {
.pointer-events-none {
pointer-events: none;
}
.absolute {
position: absolute;
}
.relative {
position: relative;
}
.inset-0 {
inset: 0px;
}
.z-0 {
z-index: 0;
}
.z-10 {
z-index: 10;
}
.flex {
display: flex;
}
.inline-flex {
display: inline-flex;
}
.min-h-11 {
min-height: calc(var(--spacing) * 11);
}
.min-h-12 {
min-height: calc(var(--spacing) * 12);
}
.min-w-11 {
min-width: calc(var(--spacing) * 11);
}
.shrink-0 {
flex-shrink: 0;
}
.-translate-x-1\/2 {
--tw-translate-x: calc(calc(1 / 2 * 100%) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.-translate-y-1\/2 {
--tw-translate-y: calc(calc(1 / 2 * 100%) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.cursor-pointer {
cursor: pointer;
}
.touch-manipulation {
touch-action: manipulation;
}
.flex-wrap {
flex-wrap: wrap;
}
.items-center {
align-items: center;
}
.justify-center {
justify-content: center;
}
.gap-2 {
gap: calc(var(--spacing) * 2);
}
.gap-3 {
gap: calc(var(--spacing) * 3);
}
.overflow-hidden {
overflow: hidden;
}
.rounded-\[inherit\] {
border-radius: inherit;
}
.rounded-full {
border-radius: calc(infinity * 1px);
}
.rounded-lg {
border-radius: var(--radius);
}
.border {
border-style: var(--tw-border-style);
border-width: 1px;
}
.border-input {
border-color: var(--input);
}
.bg-background {
background-color: var(--background);
}
.bg-current {
background-color: currentcolor;
}
.bg-neutral-200 {
background-color: var(--color-neutral-200);
}
.bg-primary {
background-color: var(--primary);
}
.bg-red-600 {
background-color: var(--color-red-600);
}
.px-0 {
padding-inline: 0px;
}
.px-3\.5 {
padding-inline: calc(var(--spacing) * 3.5);
}
.px-5 {
padding-inline: calc(var(--spacing) * 5);
}
.px-8 {
padding-inline: calc(var(--spacing) * 8);
}
.py-0 {
padding-block: 0px;
}
.text-base {
font-size: var(--text-base);
line-height: var(--tw-leading, var(--text-base--line-height));
}
.text-sm {
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
}
.text-xs {
font-size: var(--text-xs);
line-height: var(--tw-leading, var(--text-xs--line-height));
}
.font-medium {
--tw-font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-medium);
}
.whitespace-nowrap {
white-space: nowrap;
}
.text-background {
color: var(--background);
}
.text-foreground {
color: var(--foreground);
}
.text-inherit {
color: inherit;
}
.text-neutral-900 {
color: var(--color-neutral-900);
}
.text-primary {
color: var(--primary);
}
.text-white {
color: var(--color-white);
}
.underline-offset-4 {
text-underline-offset: 4px;
}
.outline {
outline-style: var(--tw-outline-style);
outline-width: 1px;
}
.transition {
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
.transition-\[background-color\,color\,filter\] {
transition-property: background-color,color,filter;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
.transition-colors {
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
.select-none {
-webkit-user-select: none;
user-select: none;
}
.\[--color-accent-foreground\:var\(--ic-accent-foreground\)\] {
--color-accent-foreground: var(--ic-accent-foreground);
}
.\[--color-accent\:var\(--ic-accent\)\] {
--color-accent: var(--ic-accent);
}
.\[--color-background\:var\(--ic-background\)\] {
--color-background: var(--ic-background);
}
.\[--color-border\:var\(--ic-border\)\] {
--color-border: var(--ic-border);
}
.\[--color-brand-soft\:var\(--ic-brand-soft\)\] {
--color-brand-soft: var(--ic-brand-soft);
}
.\[--color-brand\:var\(--ic-brand\)\] {
--color-brand: var(--ic-brand);
}
.\[--color-card-foreground\:var\(--ic-card-foreground\)\] {
--color-card-foreground: var(--ic-card-foreground);
}
.\[--color-card\:var\(--ic-card\)\] {
--color-card: var(--ic-card);
}
.\[--color-chart-1\:var\(--ic-chart-1\)\] {
--color-chart-1: var(--ic-chart-1);
}
.\[--color-chart-2\:var\(--ic-chart-2\)\] {
--color-chart-2: var(--ic-chart-2);
}
.\[--color-chart-3\:var\(--ic-chart-3\)\] {
--color-chart-3: var(--ic-chart-3);
}
.\[--color-chart-4\:var\(--ic-chart-4\)\] {
--color-chart-4: var(--ic-chart-4);
}
.\[--color-chart-5\:var\(--ic-chart-5\)\] {
--color-chart-5: var(--ic-chart-5);
}
.\[--color-destructive\:var\(--ic-destructive\)\] {
--color-destructive: var(--ic-destructive);
}
.\[--color-foreground\:var\(--ic-foreground\)\] {
--color-foreground: var(--ic-foreground);
}
.\[--color-input\:var\(--ic-input\)\] {
--color-input: var(--ic-input);
}
.\[--color-muted-foreground\:var\(--ic-muted-foreground\)\] {
--color-muted-foreground: var(--ic-muted-foreground);
}
.\[--color-muted\:var\(--ic-muted\)\] {
--color-muted: var(--ic-muted);
}
.\[--color-paper\:var\(--ic-paper\)\] {
--color-paper: var(--ic-paper);
}
.\[--color-popover-foreground\:var\(--ic-popover-foreground\)\] {
--color-popover-foreground: var(--ic-popover-foreground);
}
.\[--color-primary\:var\(--ic-primary\)\] {
--color-primary: var(--ic-primary);
}
.\[--color-ring\:var\(--ic-ring\)\] {
--color-ring: var(--ic-ring);
}
.\[--color-secondary\:var\(--ic-secondary\)\] {
--color-secondary: var(--ic-secondary);
}
.\[--ic-accent-foreground\:\#111111\] {
--ic-accent-foreground: #111111;
}
.\[--ic-accent\:\#f3f5f8\] {
--ic-accent: #f3f5f8;
}
.\[--ic-background\:\#ffffff\] {
--ic-background: #ffffff;
}
.\[--ic-border\:\#e3e7ec\] {
--ic-border: #e3e7ec;
}
.\[--ic-brand-soft\:\#bae6fd\] {
--ic-brand-soft: #bae6fd;
}
.\[--ic-brand\:\#0ea5e9\] {
--ic-brand: #0ea5e9;
}
.\[--ic-card-foreground\:\#111111\] {
--ic-card-foreground: #111111;
}
.\[--ic-card\:\#ffffff\] {
--ic-card: #ffffff;
}
.\[--ic-chart-1\:oklch\(0\.52_0\.19_254\)\] {
--ic-chart-1: oklch(0.52 0.19 254);
}
.\[--ic-chart-2\:oklch\(0\.74_0\.11_232\)\] {
--ic-chart-2: oklch(0.74 0.11 232);
}
.\[--ic-chart-3\:oklch\(0\.42_0\.16_262\)\] {
--ic-chart-3: oklch(0.42 0.16 262);
}
.\[--ic-chart-4\:oklch\(0\.84_0\.07_228\)\] {
--ic-chart-4: oklch(0.84 0.07 228);
}
.\[--ic-chart-5\:oklch\(0\.62_0\.14_240\)\] {
--ic-chart-5: oklch(0.62 0.14 240);
}
.\[--ic-destructive\:\#dc2626\] {
--ic-destructive: #dc2626;
}
.\[--ic-foreground\:\#111111\] {
--ic-foreground: #111111;
}
.\[--ic-input\:\#e3e7ec\] {
--ic-input: #e3e7ec;
}
.\[--ic-muted-foreground\:\#6d7480\] {
--ic-muted-foreground: #6d7480;
}
.\[--ic-muted\:\#f5f7fa\] {
--ic-muted: #f5f7fa;
}
.\[--ic-paper\:\#fcfcfd\] {
--ic-paper: #fcfcfd;
}
.\[--ic-popover-foreground\:\#111111\] {
--ic-popover-foreground: #111111;
}
.\[--ic-primary\:\#111111\] {
--ic-primary: #111111;
}
.\[--ic-ring\:rgba\(17\,17\,17\,0\.16\)\] {
--ic-ring: rgba(17,17,17,0.16);
}
.\[--ic-secondary\:\#646b75\] {
--ic-secondary: #646b75;
}
.\[--ic-shadow-soft\:0_18px_38px_-24px_rgba\(15\,23\,42\,0\.35\)\] {
--ic-shadow-soft: 0 18px 38px -24px rgba(15,23,42,0.35);
}
.\[--ic-surface-border\:\#e9edf2\] {
--ic-surface-border: #e9edf2;
}
@media (hover: hover) {
.hover\:bg-accent\/60:hover {
background-color: var(--accent);
}
@supports (color: color-mix(in lab, red, red)) {
.hover\:bg-accent\/60:hover {
background-color: color-mix(in oklab, var(--accent) 60%, transparent);
}
}
.hover\:bg-neutral-300:hover {
background-color: var(--color-neutral-300);
}
.hover\:bg-primary\/90:hover {
background-color: var(--primary);
}
@supports (color: color-mix(in lab, red, red)) {
.hover\:bg-primary\/90:hover {
background-color: color-mix(in oklab, var(--primary) 90%, transparent);
}
}
.hover\:bg-red-600\/90:hover {
background-color: color-mix(in srgb, oklch(57.7% 0.245 27.325) 90%, transparent);
}
@supports (color: color-mix(in lab, red, red)) {
.hover\:bg-red-600\/90:hover {
background-color: color-mix(in oklab, var(--color-red-600) 90%, transparent);
}
}
.hover\:text-accent-foreground:hover {
color: var(--accent-foreground);
}
.hover\:underline:hover {
text-decoration-line: underline;
}
}
.focus-visible\:ring-2:focus-visible {
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.focus-visible\:ring-ring:focus-visible {
--tw-ring-color: var(--ring);
}
.focus-visible\:ring-offset-2:focus-visible {
--tw-ring-offset-width: 2px;
--tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
}
.focus-visible\:outline-none:focus-visible {
--tw-outline-style: none;
outline-style: none;
}
.disabled\:pointer-events-none:disabled {
pointer-events: none;
}
.disabled\:opacity-50:disabled {
opacity: 50%;
}
.data-\[pressed\=true\]\:brightness-95[data-pressed="true"] {
--tw-brightness: brightness(95%);
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
}
.dark\:bg-neutral-600:where(.dark, .dark *) {
background-color: var(--color-neutral-600);
}
.dark\:text-neutral-50:where(.dark, .dark *) {
color: var(--color-neutral-50);
}
.dark\:\[--ic-accent-foreground\:\#f6f3ec\]:where(.dark, .dark *) {
--ic-accent-foreground: #f6f3ec;
}
.dark\:\[--ic-accent\:\#1a1a18\]:where(.dark, .dark *) {
--ic-accent: #1a1a18;
}
.dark\:\[--ic-background\:\#111111\]:where(.dark, .dark *) {
--ic-background: #111111;
}
.dark\:\[--ic-border\:\#2b2a25\]:where(.dark, .dark *) {
--ic-border: #2b2a25;
}
.dark\:\[--ic-brand-soft\:\#0c4a6e\]:where(.dark, .dark *) {
--ic-brand-soft: #0c4a6e;
}
.dark\:\[--ic-brand\:\#38bdf8\]:where(.dark, .dark *) {
--ic-brand: #38bdf8;
}
.dark\:\[--ic-card-foreground\:\#f6f3ec\]:where(.dark, .dark *) {
--ic-card-foreground: #f6f3ec;
}
.dark\:\[--ic-card\:\#111111\]:where(.dark, .dark *) {
--ic-card: #111111;
}
.dark\:\[--ic-chart-1\:oklch\(0\.68_0\.17_250\)\]:where(.dark, .dark *) {
--ic-chart-1: oklch(0.68 0.17 250);
}
.dark\:\[--ic-chart-2\:oklch\(0\.82_0\.09_225\)\]:where(.dark, .dark *) {
--ic-chart-2: oklch(0.82 0.09 225);
}
.dark\:\[--ic-chart-3\:oklch\(0\.58_0\.15_260\)\]:where(.dark, .dark *) {
--ic-chart-3: oklch(0.58 0.15 260);
}
.dark\:\[--ic-chart-4\:oklch\(0\.75_0\.12_235\)\]:where(.dark, .dark *) {
--ic-chart-4: oklch(0.75 0.12 235);
}
.dark\:\[--ic-chart-5\:oklch\(0\.88_0\.06_220\)\]:where(.dark, .dark *) {
--ic-chart-5: oklch(0.88 0.06 220);
}
.dark\:\[--ic-destructive\:\#f87171\]:where(.dark, .dark *) {
--ic-destructive: #f87171;
}
.dark\:\[--ic-foreground\:\#f6f3ec\]:where(.dark, .dark *) {
--ic-foreground: #f6f3ec;
}
.dark\:\[--ic-input\:\#2b2a25\]:where(.dark, .dark *) {
--ic-input: #2b2a25;
}
.dark\:\[--ic-muted-foreground\:\#9a958a\]:where(.dark, .dark *) {
--ic-muted-foreground: #9a958a;
}
.dark\:\[--ic-muted\:\#171716\]:where(.dark, .dark *) {
--ic-muted: #171716;
}
.dark\:\[--ic-paper\:\#171716\]:where(.dark, .dark *) {
--ic-paper: #171716;
}
.dark\:\[--ic-popover-foreground\:\#f6f3ec\]:where(.dark, .dark *) {
--ic-popover-foreground: #f6f3ec;
}
.dark\:\[--ic-primary\:\#f6f3ec\]:where(.dark, .dark *) {
--ic-primary: #f6f3ec;
}
.dark\:\[--ic-ring\:rgba\(246\,243\,236\,0\.18\)\]:where(.dark, .dark *) {
--ic-ring: rgba(246,243,236,0.18);
}
.dark\:\[--ic-secondary\:\#cbc6bb\]:where(.dark, .dark *) {
--ic-secondary: #cbc6bb;
}
.dark\:\[--ic-shadow-soft\:0_20px_44px_-28px_rgba\(0\,0\,0\,0\.6\)\]:where(.dark, .dark *) {
--ic-shadow-soft: 0 20px 44px -28px rgba(0,0,0,0.6);
}
.dark\:\[--ic-surface-border\:\#2a2a25\]:where(.dark, .dark *) {
--ic-surface-border: #2a2a25;
}
@media (hover: hover) {
.dark\:hover\:bg-neutral-500:where(.dark, .dark *):hover {
background-color: var(--color-neutral-500);
}
}
.\[\&_svg\]\:pointer-events-none svg {
pointer-events: none;
}
.\[\&_svg\]\:size-4 svg {
width: calc(var(--spacing) * 4);
height: calc(var(--spacing) * 4);
}
.\[\&_svg\]\:shrink-0 svg {
flex-shrink: 0;
}
}
:root {
--background: #fff;
--foreground: #111;
--primary: #171717;
--primary-foreground: #fff;
--secondary: #eee;
--secondary-foreground: #171717;
--muted: #eee;
--muted-foreground: #666;
--accent: #eee;
--accent-foreground: #171717;
--border: #ddd;
--input: #ddd;
--ring: #777;
--destructive: #dc2626;
--destructive-foreground: #fff;
--popover: #fff;
--popover-foreground: #111;
--success: #15803d;
--success-foreground: #fff;
--radius: .625rem;
--hairline: 1px;
}
.dark {
--background: #090909;
--foreground: #fafafa;
--primary: #fafafa;
--primary-foreground: #171717;
--secondary: #262626;
--secondary-foreground: #fafafa;
--muted: #262626;
--muted-foreground: #a3a3a3;
--accent: #262626;
--accent-foreground: #fafafa;
--border: #333;
--input: #333;
--popover: #171717;
--popover-foreground: #fafafa;
}
:root {
--header-height-mobile: 3.5rem;
--header-height-desktop: 3.5rem;
--nav-stack-height-mobile: var(--header-height-mobile);
--nav-stack-height-desktop: var(--header-height-desktop);
--background: #ffffff;
--foreground: #111111;
--primary: #111111;
--primary-foreground: #ffffff;
--secondary: #646b75;
--secondary-foreground: #ffffff;
--surface-border: #e9edf2;
--border: #e3e7ec;
--card: #ffffff;
--card-foreground: #111111;
--muted: #f5f7fa;
--muted-foreground: #6d7480;
--accent: #f3f5f8;
--accent-foreground: #111111;
--input: #e3e7ec;
--ring: rgba(17, 17, 17, 0.16);
--destructive: #dc2626;
--accent-pro: #0ea5e9;
--sidebar-accent: #f5f7fa;
--sidebar-accent-foreground: #111111;
--separator-dash: rgb(227 231 236 / 1);
--selection-bg: #cfeeff;
--selection-fg: #0f172a;
--nav-menu-surface: #f4f4f5;
--nav-menu-item-hover: #e4e4e7;
--shadow-card: inset 0 1px 0 rgb(255 255 255 / 88%), inset 0 -1px 0 rgb(0 0 0 / 5%), 0 1px
1px rgb(0 0 0 / 3%), 0 8px 24px rgb(0 0 0 / 4%);
--radius: 0.4rem;
}
.dark {
--background: #111111;
--foreground: #f6f3ec;
--secondary-bg: #171716;
--secondary: #cbc6bb;
--secondary-foreground: #111111;
--primary: #f6f3ec;
--primary-foreground: #111111;
--surface-border: #2a2a25;
--border: #2b2a25;
--card: #111111;
--card-foreground: #f6f3ec;
--muted: #171716;
--muted-foreground: #9a958a;
--accent: #1a1a18;
--accent-foreground: #f6f3ec;
--input: #2b2a25;
--ring: rgba(246, 243, 236, 0.18);
--destructive: #f87171;
--accent-pro: #38bdf8;
--sidebar-accent: #262626;
--sidebar-accent-foreground: #fff;
--separator-dash: rgb(92 91 84 / 1);
--selection-bg: #cfeeff;
--selection-fg: #0f172a;
--nav-menu-surface: #1a1a1c;
--nav-menu-item-hover: #262628;
}
.dark .hero-brush-underline::after {
background: #16a34a;
}
@keyframes status-blink {
0%, 100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.28;
transform: scale(0.82);
}
}
@keyframes iconiq-accordion-down {
from {
height: 0;
}
to {
height: var(--radix-accordion-content-height);
}
}
@keyframes iconiq-accordion-up {
from {
height: var(--radix-accordion-content-height);
}
to {
height: 0;
}
}
.dark main .text-neutral-500 {
color: rgb(163 163 163);
}
.dark main .text-neutral-600 {
color: rgb(212 212 212);
}
.dark main .text-neutral-700 {
color: rgb(230 230 230);
}
.dark main .text-neutral-800 {
color: rgb(245 245 245);
}
.dark main .text-neutral-900 {
color: rgb(255 255 255);
}
.dark main .text-neutral-400 {
color: rgb(163 163 163);
}
.dark main code {
color: rgb(212 212 212);
}
.dark main code.rounded, .dark main code.rounded-md, .dark main code[class*="bg-neutral-200"], .dark main code[class*="bg-neutral-50"] {
background-color: rgb(38 38 38);
color: rgb(230 230 230);
}
.dark code.rounded, .dark code.rounded-md, .dark code[class*="bg-neutral-200"] {
background-color: rgb(38 38 38);
color: rgb(230 230 230);
}
.dark code[class*="bg-amber-200"] {
background-color: rgb(69 69 40);
color: rgb(253 253 230);
}
.dark main a.text-neutral-700 {
color: rgb(230 230 230);
}
.dark main a:hover {
color: rgb(255 255 255);
}
.dark html[data-route-split-docs] [data-code-block] .shiki, .dark html[data-route-split-docs] [data-code-block] .shiki span {
color: var(--shiki-dark) !important;
}
@property --tw-translate-x {
syntax: "*";
inherits: false;
initial-value: 0;
}
@property --tw-translate-y {
syntax: "*";
inherits: false;
initial-value: 0;
}
@property --tw-translate-z {
syntax: "*";
inherits: false;
initial-value: 0;
}
@property --tw-border-style {
syntax: "*";
inherits: false;
initial-value: solid;
}
@property --tw-font-weight {
syntax: "*";
inherits: false;
}
@property --tw-outline-style {
syntax: "*";
inherits: false;
initial-value: solid;
}
@property --tw-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-shadow-color {
syntax: "*";
inherits: false;
}
@property --tw-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-inset-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-inset-shadow-color {
syntax: "*";
inherits: false;
}
@property --tw-inset-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-ring-color {
syntax: "*";
inherits: false;
}
@property --tw-ring-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-inset-ring-color {
syntax: "*";
inherits: false;
}
@property --tw-inset-ring-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-ring-inset {
syntax: "*";
inherits: false;
}
@property --tw-ring-offset-width {
syntax: "<length>";
inherits: false;
initial-value: 0px;
}
@property --tw-ring-offset-color {
syntax: "*";
inherits: false;
initial-value: #fff;
}
@property --tw-ring-offset-shadow {
syntax: "*";
inherits: false;
initial-value: 0 0 #0000;
}
@property --tw-blur {
syntax: "*";
inherits: false;
}
@property --tw-brightness {
syntax: "*";
inherits: false;
}
@property --tw-contrast {
syntax: "*";
inherits: false;
}
@property --tw-grayscale {
syntax: "*";
inherits: false;
}
@property --tw-hue-rotate {
syntax: "*";
inherits: false;
}
@property --tw-invert {
syntax: "*";
inherits: false;
}
@property --tw-opacity {
syntax: "*";
inherits: false;
}
@property --tw-saturate {
syntax: "*";
inherits: false;
}
@property --tw-sepia {
syntax: "*";
inherits: false;
}
@property --tw-drop-shadow {
syntax: "*";
inherits: false;
}
@property --tw-drop-shadow-color {
syntax: "*";
inherits: false;
}
@property --tw-drop-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-drop-shadow-size {
syntax: "*";
inherits: false;
}
@layer properties {
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
*, ::before, ::after, ::backdrop {
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-translate-z: 0;
--tw-border-style: solid;
--tw-font-weight: initial;
--tw-outline-style: solid;
--tw-shadow: 0 0 #0000;
--tw-shadow-color: initial;
--tw-shadow-alpha: 100%;
--tw-inset-shadow: 0 0 #0000;
--tw-inset-shadow-color: initial;
--tw-inset-shadow-alpha: 100%;
--tw-ring-color: initial;
--tw-ring-shadow: 0 0 #0000;
--tw-inset-ring-color: initial;
--tw-inset-ring-shadow: 0 0 #0000;
--tw-ring-inset: initial;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-offset-shadow: 0 0 #0000;
--tw-blur: initial;
--tw-brightness: initial;
--tw-contrast: initial;
--tw-grayscale: initial;
--tw-hue-rotate: initial;
--tw-invert: initial;
--tw-opacity: initial;
--tw-saturate: initial;
--tw-sepia: initial;
--tw-drop-shadow: initial;
--tw-drop-shadow-color: initial;
--tw-drop-shadow-alpha: 100%;
--tw-drop-shadow-size: initial;
}
}
}