Skip to Content

Default Variants

defaultVariants lets consumers omit props and still get a styled component. This page covers when to use them, common pitfalls, and patterns for sensible defaults.

defaultVariants

defaultVariants sets fallback values used when the consumer omits a variant.

const button = ev({
  base: {
    border: '1px solid transparent',
    borderRadius: 8,
    cursor: 'pointer',
    fontWeight: 600,
    transition: 'all 0.2s ease',
  },
  variants: {
    variant: {
      filled: {
        background: 'var(--button-color)',
        color: 'white',
      },
      outlined: {
        background: 'transparent',
        borderColor: 'var(--button-color)',
        color: 'var(--button-color)',
      },
      text: {
        background: 'transparent',
        color: 'var(--button-color)',
      },
    },
    size: {
      small: {
        padding: '8px 16px',
        fontSize: 12,
      },
      medium: {
        padding: '10px 20px',
        fontSize: 14,
      },
      large: {
        padding: '12px 24px',
        fontSize: 16,
      },
    },
    color: {
      primary: {
        '--button-color': '#d36ac2',
      },
      secondary: {
        '--button-color': '#475569',
      },
    },
  },
  defaultVariants: {
    variant: 'filled',
    size: 'medium',
    color: 'primary',
  },
});

<button css={button()}>Default</button>
<button
  css={button({
    variant: 'outlined',
  })}
>
  Outlined
</button>
<button
  css={button({
    size: 'small',
  })}
>
  Small
</button>

Set defaults that match the most common case

If 80% of <Button> calls in your codebase use size: 'medium' and color: 'primary', those should be the defaults.

const button = ev({
  variants: {
    size: {
      small: {},
      medium: {},
      large: {},
    },
    color: {
      primary: {},
      secondary: {},
      danger: {},
    },
  },
  defaultVariants: {
    size: 'medium',
    color: 'primary',
  },
});

button();                  // medium primary
button({ size: 'large' }); // large primary

Forward optional props straight through

Each key is resolved with props[key] ?? defaultVariants[key], so undefined and null both fall back to the default. You can hand an optional prop to the variant function without guarding it.

type Props = {
  size?: 'small' | 'medium' | 'large';
};

function Button({ size }: Props) {
  // `size` is undefined when the prop is omitted — 'medium' still applies.
  return <button css={button({ size })}>Click</button>;
}
button({ size: undefined }); // identical to button() — medium primary

null falls back the same way at runtime, but EvVariants does not include it — under strictNullChecks a nullable value has to be normalized to undefined before it is passed.

This means you don't need a prop default ({ size = 'medium' }) on top of defaultVariants. Pick one place to declare the default, and defaultVariants is the one that also covers direct button() calls from outside the component.

false and 0 are valid variant values, so they are used as-is rather than falling back.

const badge = ev({
  variants: {
    outlined: {
      true: {
        border: '1px solid',
      },
      false: {
        border: 'none',
      },
    },
  },
  defaultVariants: {
    outlined: true,
  },
});

badge({ outlined: false }); // border: none, not the default `true` style

Defaults for boolean variants

Use literal true / false, not the string keys:

const chip = ev({
  variants: {
    outlined: {
      true: {
        border: '1px solid',
      },
      false: {
        border: 'none',
      },
    },
  },
  defaultVariants: {
    outlined: false,
  },
});

chip(); // applies the `false` style

Defaults play well with compound variants

Defaults seed the variant values used to evaluate compound matches.

const button = ev({
  variants: {
    size: {
      small: {},
      large: {},
    },
    color: {
      primary: {},
      danger: {},
    },
  },
  compoundVariants: [
    {
      variants: {
        size: 'large',
        color: 'primary',
      },
      style: {
        fontWeight: 'bold',
      },
    },
  ],
  defaultVariants: {
    size: 'large',
    color: 'primary',
  },
});

button(); // bold because defaults satisfy the compound condition

See also

Edit on GitHub

Last updated on

On this page