Skip to Content

Compound Variants

compound variants 只在你列出的 variant 值全部匹配时才应用样式。它解决的是“我只想在 variant: 'outlined' 且 color: 'danger' 时应用这个样式”这类需求。也可以只列出一个维度,此时它就像一个在其他样式之后才应用的 variant 样式。

compoundVariants

compound variant 只在多个维度同时匹配特定值时应用样式,适合“X 同时是 Y 时外观不同”的模式。

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',
      },
    },
  },
  compoundVariants: [
    {
      variants: {
        variant: 'filled',
        size: 'large',
        color: 'primary',
      },
      style: {
        boxShadow: '0 4px 12px rgba(211, 106, 194, 0.4)',
      },
    },
  ],
  defaultVariants: {
    variant: 'filled',
    size: 'medium',
    color: 'primary',
  },
});

<button
  css={button({
    size: 'large',
    color: 'primary',
  })}
>
  Large primary
</button>
<button
  css={button({
    size: 'large',
    color: 'secondary',
  })}
>
  Large secondary
</button>

compound variants 按数组顺序求值,并叠加在匹配的普通 variants 之后,因此可以放心地覆盖样式。

何时使用

以下情况适合使用 compound variant:

  • 某个样式同时依赖两个或更多维度。
  • 如果用单一维度的 variant 来表达,就得新增维度或重复定义值。
  • 组件内部开始出现条件 CSS(size === 'lg' && color === 'primary' && ...)。

如果样式只依赖一个维度,直接写进 variants 即可。compound variants 用于维度之间的交叉组合。

模式:filled / outlined / text × color

一种常见的设计系统模式:同一组颜色,用三种方式呈现。

import { ev } from 'emotion-variants';

const button = ev({
  base: {
    padding: '8px 16px',
    borderRadius: 8,
    cursor: 'pointer',
    border: 'none',
  },

  variants: {
    variant: {
      filled: {},
      outlined: {
        background: 'transparent',
        border: '2px solid currentColor',
      },
      text: {
        background: 'transparent',
      },
    },
    color: {
      primary: {},
      danger: {},
    },
  },

  compoundVariants: [
    // Filled
    {
      variants: {
        variant: 'filled',
        color: 'primary',
      },
      style: {
        background: '#d36ac2',
        color: 'white',
      },
    },
    {
      variants: {
        variant: 'filled',
        color: 'danger',
      },
      style: {
        background: '#dc2626',
        color: 'white',
      },
    },

    // Outlined
    {
      variants: {
        variant: 'outlined',
        color: 'primary',
      },
      style: {
        color: '#d36ac2',
      },
    },
    {
      variants: {
        variant: 'outlined',
        color: 'danger',
      },
      style: {
        color: '#dc2626',
      },
    },

    // Text
    {
      variants: {
        variant: 'text',
        color: 'primary',
      },
      style: {
        color: '#d36ac2',
      },
    },
    {
      variants: {
        variant: 'text',
        color: 'danger',
      },
      style: {
        color: '#dc2626',
      },
    },
  ],

  defaultVariants: {
    variant: 'filled',
    color: 'primary',
  },
});

color variant 在 variants 中是空对象,因为它的样式只有在与 variant 组合时才有意义。实际的值由 compound variants 承载。

模式:size × density 覆盖

有时 compound variant 只在某个特定交叉点上微调基础属性。

const card = ev({
  base: {
    padding: 16,
    borderRadius: 12,
  },
  variants: {
    size: {
      sm: {
        padding: 12,
      },
      md: {
        padding: 16,
      },
      lg: {
        padding: 24,
      },
    },
    density: {
      comfortable: {},
      compact: {},
    },
  },
  compoundVariants: [
    {
      variants: {
        size: 'lg',
        density: 'compact',
      },
      style: {
        padding: 16,
      },
    },
  ],
});

只有 lg + compact 这个组合会覆盖该尺寸原本的 padding。

匹配多个值

条件可以是数组。只要 variant 的值是列出值中的任意一个就算匹配,因此一个条目就能覆盖原本要逐个复制的情况。

const button = ev({
  variants: {
    variant: {
      filled: {},
      outlined: {},
      text: {},
    },
    color: {
      primary: {},
      danger: {},
    },
  },
  compoundVariants: [
    {
      variants: {
        variant: ['outlined', 'text'],
        color: 'danger',
      },
      style: {
        color: 'crimson',
      },
    },
  ],
});

button({ variant: 'outlined', color: 'danger' }); // 包含 compound 样式
button({ variant: 'text', color: 'danger' }); // 同样包含
button({ variant: 'filled', color: 'danger' }); // 不包含

维度之间仍然是 AND 关系。列出的每个维度都必须匹配;数组只是放宽了它所在维度的匹配范围。空数组永远不会匹配。

顺序很重要

每次调用时,compound variants 会按在数组中出现的顺序追加在普通 variants 之后。如果两个 compound variant 都匹配并设置了同一属性,后者生效。

compoundVariants: [
  {
    variants: {
      size: 'lg',
    },
    style: {
      fontWeight: 600,
    },
  },
  {
    variants: {
      size: 'lg',
      color: 'primary',
    },
    style: {
      fontWeight: 700, // 覆盖上面的条目
    },
  },
],

compound variant 也可以只匹配单个维度。它的行为与普通 variant 相同,但当你想把所有“条件”规则集中在一处时很有用。

另请参阅

本页目录