Skip to main content
import { styled } from "better-styled";

const Button = styled("button", {
  base: { className: "px-4 py-2 rounded" },
  variants: {
    size: {
      sm: { className: "text-sm" },
      lg: { className: "text-lg" },
    },
  },
});

Configuration

base

Props that always apply, regardless of variants.
const Card = styled("div", {
  base: {
    className: "rounded-xl border shadow-sm",
    role: "article",
  },
});
Any valid prop for the component works here: className, style, event handlers, ARIA attributes, etc.

variants

Define your variant options. Each variant is a named group with multiple options.
const Button = styled("button", {
  variants: {
    size: {
      sm: { className: "h-8 px-3 text-sm" },
      md: { className: "h-10 px-4 text-base" },
      lg: { className: "h-12 px-6 text-lg" },
    },
    intent: {
      primary: { className: "bg-blue-600 text-white" },
      danger: { className: "bg-red-600 text-white" },
      ghost: { className: "bg-transparent" },
    },
  },
});

// Usage
<Button size="lg" intent="danger">Delete</Button>

defaultVariants

Set which variant values apply when not specified.
const Button = styled("button", {
  variants: {
    size: {
      sm: { className: "text-sm" },
      md: { className: "text-base" },
      lg: { className: "text-lg" },
    },
  },
  defaultVariants: {
    size: "md",
  },
});

// These are equivalent:
<Button />
<Button size="md" />

compoundVariants

Apply styles when specific variant combinations occur.
const Button = styled("button", {
  variants: {
    color: {
      primary: { className: "text-blue-600" },
      danger: { className: "text-red-600" },
    },
    variant: {
      solid: { className: "text-white" },
      outline: { className: "bg-transparent border-2" },
    },
  },
  compoundVariants: [
    {
      color: "primary",
      variant: "solid",
      props: { className: "bg-blue-600" },
    },
    {
      color: "primary",
      variant: "outline",
      props: { className: "border-blue-600" },
    },
    {
      color: "danger",
      variant: "solid",
      props: { className: "bg-red-600" },
    },
    {
      color: "danger",
      variant: "outline",
      props: { className: "border-red-600" },
    },
  ],
});
When color="primary" AND variant="outline", the matching compound variant applies.

context

Connect to a shared context for variant propagation. See Context for details.
const ButtonContext = createStyledContext({
  size: ["sm", "md", "lg"],
});

const Button = styled("button", {
  context: ButtonContext,
  // variants automatically sync with context
});

Props Merging

better-styled intelligently merges props from different sources.

Priority Order

From lowest to highest priority:
  1. 🥉 base
  2. 🥈 variants
  3. 🥈 compoundVariants
  4. 🥇 Props passed directly to the component
const Button = styled("button", {
  base: { className: "px-4" },
  variants: {
    size: {
      lg: { className: "px-8" }, // overrides base px-4
    },
  },
});

// Direct props have highest priority
<Button size="lg" className="px-12">
  {/* Final className: "px-12" (direct prop wins) */}
</Button>

className

Classes are merged using tailwind-merge, which handles Tailwind conflicts intelligently.
const Button = styled("button", {
  base: { className: "px-4 py-2 bg-blue-500" },
  variants: {
    size: {
      lg: { className: "px-8 py-4" }, // px and py override, bg stays
    },
  },
});

style

Style objects are merged with Object.assign. Later values override earlier ones.
const Box = styled("div", {
  base: { style: { padding: 16, margin: 8 } },
  variants: {
    spacing: {
      large: { style: { padding: 32 } }, // only padding overrides
    },
  },
});

Event Handlers

Here’s something special: event handlers are composed, not overwritten.
const Button = styled("button", {
  base: {
    onClick: () => console.log("base click"),
  },
  variants: {
    tracked: {
      true: {
        onClick: () => console.log("tracked click"),
      },
    },
  },
});

// Both handlers run!
<Button tracked onClick={() => console.log("user click")}>
  {/* Logs: "base click", "tracked click", "user click" */}
</Button>
This is powerful for analytics, logging, or any case where you want to add behavior without replacing it.

Works With Any Component

styled() works with HTML elements, React components, and React Native components.
// HTML element
const Button = styled("button", { ... });

// React component
const StyledLink = styled(Link, { ... });

// React Native
const StyledPressable = styled(Pressable, { ... });
The only requirement: the component must accept the props you define in your variants.

TypeScript

Types are fully inferred. No manual typing needed.
const Button = styled("button", {
  variants: {
    size: {
      sm: {},
      md: {},
      lg: {},
    },
  },
});

// ✅ TypeScript knows: size?: "sm" | "md" | "lg"
<Button size="xl" /> // ❌ Error: "xl" is not valid

Next: Variants

Deep dive into the variant system