Skip to Content

Slots

컴포넌트가 요소 하나로 끝나는 경우는 드물어요. 카드에는 root, title, footer가 있고, size 같은 variant는 보통 그중 여러 개를 한꺼번에 바꿔요. slots를 쓰면 ev() 호출 하나로 모든 부분을 스타일링할 수 있어요. 부분들이 같은 variant를 공유하니 서로 어긋날 일이 없어요.

부분 선언하기

base 대신 slots를 써요. 각 키는 컴포넌트의 한 부분을 가리키고, 그 부분의 기본 스타일을 담아요.

import { ev } from 'emotion-variants';

const card = ev({
  slots: {
    root: {
      borderRadius: 12,
      padding: 16,
    },
    title: {
      fontWeight: 600,
    },
    footer: {
      display: 'flex',
      gap: 8,
    },
  },
});

이제 card()를 호출하면 slot마다 스타일 함수를 하나씩 돌려줘요. 각 요소를 렌더링하는 자리에서 호출하세요.

import type { ComponentPropsWithoutRef, ReactNode } from 'react';

type CardProps = ComponentPropsWithoutRef<'section'> & {
  heading: ReactNode;
};

function Card({ heading, children, ...rest }: CardProps) {
  const { root, title, footer } = card();

  return (
    <section css={root()} {...rest}>
      <h3 css={title()}>{heading}</h3>
      <div css={footer()}>{children}</div>
    </section>
  );
}

slot은 스타일이 아니라 함수예요. css={root()}로 쓰세요. Emotion은 css에 넘긴 함수를 theme과 함께 호출해요. 그래서 css={root}로 쓰면 theme 객체가 slot의 variants로 읽혀요.

slot별 variant

slots가 있으면 variant 값은 slot 이름과 스타일을 짝지어요. 값에는 바꾸려는 slot만 적으면 돼요.

const card = ev({
  slots: {
    root: {
      display: 'flex',
      flexDirection: 'column',
      gap: 6,
      width: 320,
      border: '1px solid #e4e4e7',
      borderRadius: 12,
      background: 'white',
      color: '#09090b',
      boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)',
    },
    title: {
      fontWeight: 600,
    },
    footer: {
      display: 'flex',
      gap: 8,
      fontSize: 14,
      color: '#71717a',
    },
  },
  variants: {
    size: {
      small: {
        root: {
          padding: 16,
        },
        title: {
          fontSize: 16,
        },
      },
      large: {
        root: {
          padding: 24,
        },
        title: {
          fontSize: 18,
        },
      },
    },
    tone: {
      neutral: {},
      accent: {
        title: {
          color: '#d36ac2',
        },
      },
    },
  },
  defaultVariants: {
    size: 'small',
    tone: 'neutral',
  },
});

const { root, title, footer } = card({
  size: 'large',
  tone: 'accent',
});

<div css={root()}>
  <h3 css={title()}>Weekly report</h3>
  <div css={footer()}>Updated 2 hours ago</div>
</div>

호출 한 번으로 모든 slot의 variant가 정해져요. root()는 large의 padding을, title()은 large의 font size와 accent의 color를 받아요. footer()는 어떤 variant도 언급하지 않아서 기본 스타일만 받아요.

한 slot에서만 variant 바꾸기

각 slot 함수는 호출할 때와 같은 variant props를 받아요. slot props가 호출 props를 이기고, 호출 props가 defaultVariants를 이겨요.

const { root, title } = card({
  size: 'large',
});

root(); // large
title({
  size: 'small',
}); // title만 small

다른 곳과 마찬가지로 undefined는 다음 단계로 떨어져요. 그래서 선택 prop을 slot에 그대로 넘겨도 안전해요.

Compound variants

compoundVariants는 똑같이 동작하고, style만 slot 이름으로 나눠 적어요.

compoundVariants: [
  {
    variants: {
      size: 'large',
      tone: 'accent',
    },
    style: {
      title: {
        textTransform: 'uppercase',
      },
    },
  },
],

복합 variant는 slot마다, 그 slot에서 정해진 값을 기준으로 일치 여부를 따져요. 그래서 한 slot에서 variant를 바꾸면 그 slot에서만 복합 variant가 켜지거나 꺼질 수 있어요.

const { title } = card({
  tone: 'accent',
});

title(); // small + accent: 복합 스타일 없음
title({
  size: 'large',
}); // large + accent: uppercase

여러 slot에 같은 스타일 적용하기

compoundSlots는 스타일 하나를 여러 slot에 적용해요. slot 이름마다 같은 스타일을 반복해서 적지 않아도 돼요.

compoundSlots: [
  {
    slots: ['title', 'footer'],
    style: {
      paddingInline: 4,
    },
  },
  {
    slots: ['root', 'footer'],
    variants: {
      size: 'large',
    },
    style: {
      gap: 16,
    },
  },
],

variants는 선택이에요. 없으면 항상 적용되고, 있으면 compoundVariants와 같은 규칙으로 일치 여부를 따져요. 배열 조건도 쓸 수 있어요.

순서

slot마다 스타일은 이 순서로 쌓이고, CSS가 충돌하면 뒤에 오는 게 이겨요.

  1. slots에 적은 그 slot의 기본 스타일
  2. 일치하는 variants 항목 (축을 선언한 순서대로)
  3. 일치하는 compoundVariants 항목 (배열 순서대로)
  4. 일치하는 compoundSlots 항목 (배열 순서대로)

class name

className()은 slot마다 class name을 하나씩 반환해요.

const classNames = card.className({
  size: 'large',
});

<section className={classNames.root}>
  <h3 className={classNames.title}>Title</h3>
</section>;

slots를 선언하면 classNames 객체는 없어요.

규칙

  • base와 slots는 함께 쓸 수 없어요. 스타일은 slot에 넣으세요. 보통 root라는 이름을 써요.
  • variant 값, 복합 variant의 style, compoundSlots 항목에는 선언한 slot 이름만 쓸 수 있어요. 그 외에는 타입 에러가 나서 오타를 잡아 줘요.
  • '&:hover' 같은 selector는 slot 이름 옆이 아니라 slot의 스타일 안에 적어요.
  • compoundSlots는 slots가 있어야 쓸 수 있어요.

Props

VariantProps는 slot 함수에도 쓸 수 있어요. 모든 slot이 공유하는 variant props를 돌려줘요.

import type { VariantProps } from 'emotion-variants';

type CardVariants = VariantProps<typeof card>;
// { size?: 'small' | 'large'; tone?: 'neutral' | 'accent' }

함께 보기

GitHub에서 이 페이지 편집

마지막 업데이트

이 페이지에서