The Button component extends Base UI's Button. It adds variant, size, loading state, and icon props for common use cases.
Here is the component implementation code that you can copy to your project:
// https://devie-ui.com/components/button
// https://base-ui.com/react/components/button
import { Button as BaseButton } from "@base-ui/react/button";
import clsx from "clsx";
import styles from "./Button.module.scss";
type Variant =
| "primary"
| "secondary"
| "danger"
| "naked"
| "icon-primary"
| "icon-secondary"
| "icon-danger"
| "icon-naked";
type Size = "sm" | "md" | "xl";
function Button({
variant = "primary",
size = "md",
children,
className,
disabled,
isLoading,
...props
}: Button.Props) {
return (
<BaseButton
className={clsx(
styles.button,
variant === "primary" && styles.variantPrimary,
variant === "secondary" && styles.variantSecondary,
variant === "danger" && styles.variantDanger,
variant === "naked" && styles.variantNaked,
variant === "icon-primary" && [styles.variantPrimary, styles.icon],
variant === "icon-secondary" && [styles.variantSecondary, styles.icon],
variant === "icon-danger" && [styles.variantDanger, styles.icon],
variant === "icon-naked" && [styles.variantNaked, styles.icon],
size === "sm" && styles.sizeSm,
size === "md" && styles.sizeMd,
size === "xl" && styles.sizeXl,
className,
)}
data-loading={isLoading}
disabled={disabled || isLoading}
focusableWhenDisabled={isLoading}
{...props}
>
{children}
{isLoading && (
<div className={styles.loadingOverlay}>
<div className={styles.loader} />
</div>
)}
</BaseButton>
);
}
namespace Button {
export interface Props extends BaseButton.Props {
variant?: Variant;
size?: Size;
isLoading?: boolean;
}
export type State = BaseButton.State;
}
export default Button;The Button component offers four distinct variants to fit different UI needs: primary for main call-to-action buttons, secondary for less visually dominant actions, danger for destructive actions, and naked for minimal buttons without background.
Buttons can be sized using the size prop with three options: sm for compact buttons, md for the default medium size, and xl for larger, more prominent buttons.
The isLoading prop provides a CSS-only loader state that maintains the button's original dimensions to prevent layout shifts during loading transitions. This ensures a smooth UI experience, especially in forms or button groups where sudden width changes could disrupt the layout.
Buttons can be disabled using the standard disabled prop. When disabled, buttons show a reduced opacity and are not interactive, providing clear visual feedback to users.
The icon prop creates a square button with equal padding on all sides, perfect for icon-only buttons. This works with all variants and sizes.