40+ landing page components for ReactBrowse now

Customization

Custom Variants

Use the variants() utility to create type-safe, variant-driven component styles. Build custom button variants, extend existing styles, and compose compound variants.

The variants() Utility

variants() is a lightweight alternative to CVA (class-variance-authority). It takes a config object with base classes, variant definitions, and optional defaults, then returns a function that produces the right class string for any combination of props.

Basic variants() usage
import { variants } from "@trinkui/react";

const badge = variants({
  base: "inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium",
  variants: {
    variant: {
      default: "bg-[rgb(var(--trinkui-secondary))] text-[rgb(var(--trinkui-secondary-fg))]",
      primary: "bg-[rgb(var(--trinkui-primary))] text-[rgb(var(--trinkui-primary-fg))]",
      success: "bg-emerald-100 text-emerald-800",
      warning: "bg-amber-100 text-amber-800",
      danger: "bg-red-100 text-red-800",
    },
  },
  defaultVariants: {
    variant: "default",
  },
});

// Usage:
badge()                    // → base + default variant
badge({ variant: "primary" })  // → base + primary variant
badge({ variant: "danger" })   // → base + danger variant

Creating Custom Button Variants

Define a complete button style system with multiple variant axes:

button.styles.ts
import { variants } from "@trinkui/react";

export const buttonStyles = variants({
  base: [
    "inline-flex items-center justify-center font-medium transition-all duration-200",
    "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[rgb(var(--trinkui-primary))] focus-visible:ring-offset-2",
    "disabled:pointer-events-none disabled:opacity-50",
  ].join(" "),

  variants: {
    variant: {
      primary: "bg-[rgb(var(--trinkui-primary))] text-[rgb(var(--trinkui-primary-fg))] hover:opacity-90",
      secondary: "bg-[rgb(var(--trinkui-secondary))] text-[rgb(var(--trinkui-secondary-fg))] hover:opacity-80",
      outline: "border border-[rgb(var(--trinkui-border))] bg-transparent text-[rgb(var(--trinkui-fg))] hover:bg-[rgb(var(--trinkui-surface))]",
      ghost: "bg-transparent text-[rgb(var(--trinkui-fg))] hover:bg-[rgb(var(--trinkui-surface))]",
      danger: "bg-red-600 text-white hover:bg-red-700",
      gradient: "bg-gradient-to-r from-[rgb(var(--trinkui-primary))] to-[rgb(var(--trinkui-accent))] text-white hover:opacity-90",
    },
    size: {
      sm: "h-8 rounded-md px-3 text-xs",
      md: "h-10 rounded-lg px-4 text-sm",
      lg: "h-12 rounded-lg px-6 text-base",
      xl: "h-14 rounded-xl px-8 text-lg",
    },
  },

  defaultVariants: {
    variant: "primary",
    size: "md",
  },
});

// Usage in a component:
// className={buttonStyles({ variant: "outline", size: "lg" })}

Compound Variants

Compound variants apply additional classes when multiple variant conditions are met simultaneously. This is useful for special combinations that need unique styling:

Compound variant example
import { variants } from "@trinkui/react";

const alert = variants({
  base: "rounded-lg border p-4",
  variants: {
    variant: {
      info: "border-blue-200 bg-blue-50 text-blue-800",
      success: "border-emerald-200 bg-emerald-50 text-emerald-800",
      warning: "border-amber-200 bg-amber-50 text-amber-800",
      error: "border-red-200 bg-red-50 text-red-800",
    },
    size: {
      sm: "p-3 text-sm",
      md: "p-4 text-base",
      lg: "p-6 text-lg",
    },
  },
  compoundVariants: [
    // When variant is "error" AND size is "lg", add extra emphasis
    {
      variant: "error",
      size: "lg",
      class: "border-2 font-semibold shadow-lg shadow-red-100",
    },
    // When variant is "success" AND size is "sm", use compact styling
    {
      variant: "success",
      size: "sm",
      class: "rounded-md",
    },
  ],
  defaultVariants: {
    variant: "info",
    size: "md",
  },
});

alert({ variant: "error", size: "lg" })
// → base + error variant + lg size + compound (border-2 font-semibold shadow-lg)

Extending Existing Styles

Since variants() returns a function that produces class strings, you can compose variant functions with cn() to extend or override existing styles:

Extending styles with cn()
import { cn, variants } from "@trinkui/react";

// Base card styles
const cardStyles = variants({
  base: "rounded-lg border border-[rgb(var(--trinkui-border))] bg-[rgb(var(--trinkui-surface))] p-6",
  variants: {
    elevation: {
      flat: "",
      raised: "shadow-[var(--trinkui-shadow-md)]",
      floating: "shadow-[var(--trinkui-shadow-lg)]",
    },
  },
  defaultVariants: { elevation: "flat" },
});

// Extend with extra classes using cn()
function PricingCard({ featured, ...props }: { featured?: boolean }) {
  return (
    <div
      className={cn(
        cardStyles({ elevation: featured ? "floating" : "flat" }),
        featured && "border-[rgb(var(--trinkui-primary))] ring-1 ring-[rgb(var(--trinkui-primary)/0.3)]",
        "transition-transform hover:scale-[1.02]"
      )}
    >
      {/* card content */}
    </div>
  );
}

Full Example: Custom Tag Component

A complete example of a custom component built with variants():

Tag.tsx
import React, { forwardRef } from "react";
import { cn, variants } from "@trinkui/react";

const tagStyles = variants({
  base: "inline-flex items-center gap-1 font-medium transition-colors",
  variants: {
    variant: {
      solid: "bg-[rgb(var(--trinkui-primary))] text-[rgb(var(--trinkui-primary-fg))]",
      soft: "bg-[rgb(var(--trinkui-primary)/0.1)] text-[rgb(var(--trinkui-primary))]",
      outline: "border border-[rgb(var(--trinkui-border))] text-[rgb(var(--trinkui-fg))]",
    },
    size: {
      sm: "rounded px-1.5 py-0.5 text-[10px]",
      md: "rounded-md px-2 py-0.5 text-xs",
      lg: "rounded-md px-2.5 py-1 text-sm",
    },
    removable: {
      true: "pr-1",
      false: "",
    },
  },
  compoundVariants: [
    { variant: "outline", size: "sm", class: "border-[0.5px]" },
  ],
  defaultVariants: { variant: "soft", size: "md", removable: "false" },
});

interface TagProps extends React.HTMLAttributes<HTMLSpanElement> {
  variant?: "solid" | "soft" | "outline";
  size?: "sm" | "md" | "lg";
  removable?: boolean;
  onRemove?: () => void;
}

export const Tag = forwardRef<HTMLSpanElement, TagProps>(
  ({ variant, size, removable, onRemove, className, children, ...props }, ref) => {
    return (
      <span
        ref={ref}
        className={cn(tagStyles({ variant, size, removable: removable ? "true" : "false" }), className)}
        {...props}
      >
        {children}
        {removable && (
          <button onClick={onRemove} className="ml-0.5 rounded hover:bg-black/10" aria-label="Remove">
            x
          </button>
        )}
      </span>
    );
  }
);

Tag.displayName = "Tag";

Explore more

Now that you understand customization, explore the component library.

Browse Components