Featuring Design System

$css Prop

Rainbow Sprinkles 기반의 $css prop으로 토큰 기반 스타일링하기

Featuring Design System의 핵심 스타일링 API. Rainbow Sprinkles 기반으로, 디자인 토큰 값과 임의 CSS 값을 하나의 prop에서 씁니다.

기본 사용법

모든 레이아웃 컴포넌트(Box, Flex, HStack, VStack, Center, Grid, Typo)와 Button에서 사용합니다.

<Box $css={{
padding: '$spacing-400',
borderRadius: '$radius-200',
backgroundColor: '$background-1',
color: '$text-1',
}}>
Hello World
</Box>

토큰 값 vs 임의 값

토큰 참조와 임의 CSS 값을 모두 지원합니다. 토큰 이름을 문자열로 전달하면 CSS 변수로 변환됩니다.

<Box $css={{
// 토큰 값
padding: '$spacing-400',
borderRadius: '$radius-200',

// 임의 CSS 값
width: '100%',
maxWidth: '480px',
border: '1px solid',
borderColor: '$border-default',
}}>
토큰 + 임의 값 혼합
</Box>

토큰은 단일 값 자리에서만 치환됩니다. padding: '0 $spacing-600'처럼 shorthand 문자열 안에 토큰을 끼워 넣으면 변환이 일어나지 않고 $spacing-600이 리터럴 문자열로 출력됩니다. 비대칭 값이 필요하면 개별 프로퍼티 또는 paddingX / paddingY shorthand로 분리하세요.

// ❌ 동작하지 않음 — 토큰이 치환되지 않음
<Box $css={{ padding: '0 $spacing-600' }} />

// ✅ 개별 프로퍼티로 분리
<Box $css={{ paddingY: 0, paddingX: '$spacing-600' }} />

margin, inset, border, borderRadius 등 모든 multi-value shorthand에 동일하게 적용됩니다.

레이아웃 속성

레이아웃 관련 속성은 반응형 조건(mobile/tablet/desktop/wide)을 지원합니다.

Display & Flex

<Flex $css={{
gap: '$spacing-300',
padding: '$spacing-400',
borderRadius: '$radius-200',
border: '1px solid',
borderColor: '$border-default',
}}>
<Box $css={{
  flex: '1',
  padding: '$spacing-300',
  bgColor: '$primary-10',
  borderRadius: '$radius-100',
  textAlign: 'center',
}}>flex: 1</Box>
<Box $css={{
  flex: '2',
  padding: '$spacing-300',
  bgColor: '$primary-20',
  borderRadius: '$radius-100',
  textAlign: 'center',
}}>flex: 2</Box>
<Box $css={{
  flex: '1',
  padding: '$spacing-300',
  bgColor: '$primary-10',
  borderRadius: '$radius-100',
  textAlign: 'center',
}}>flex: 1</Box>
</Flex>

Spacing

<HStack $css={{ gap: '$spacing-300' }}>
<Box $css={{
  padding: '$spacing-200',
  bgColor: '$primary-10',
  borderRadius: '$radius-100',
}}>spacing-200</Box>
<Box $css={{
  padding: '$spacing-400',
  bgColor: '$primary-20',
  borderRadius: '$radius-100',
}}>spacing-400</Box>
<Box $css={{
  padding: '$spacing-600',
  bgColor: '$primary-30',
  borderRadius: '$radius-100',
}}>spacing-600</Box>
</HStack>

Sizing

<Box $css={{
  width: '100%',
  height: '48px',
  minWidth: '200px',
  maxWidth: '640px',
  minHeight: '100vh',
  size: '40px',          // width + height 동시 설정
}} />

Border & Radius

<HStack $css={{ gap: '$spacing-300' }}>
<Box $css={{
  size: '64px',
  bgColor: '$primary-20',
  borderRadius: '$radius-100',
  display: 'flex',
  alignItems: 'center',
  justifyContent: 'center',
}}>100</Box>
<Box $css={{
  size: '64px',
  bgColor: '$primary-30',
  borderRadius: '$radius-200',
  display: 'flex',
  alignItems: 'center',
  justifyContent: 'center',
}}>200</Box>
<Box $css={{
  size: '64px',
  bgColor: '$primary-40',
  borderRadius: '$radius-full',
  display: 'flex',
  alignItems: 'center',
  justifyContent: 'center',
  color: 'white',
}}>full</Box>
</HStack>

Grid

<Grid $css={{
gridTemplateColumns: 'repeat(3, 1fr)',
gap: '$spacing-300',
}}>
{[1,2,3,4,5,6].map(i => (
  <Box key={i} $css={{
    padding: '$spacing-400',
    bgColor: '$primary-10',
    borderRadius: '$radius-100',
    textAlign: 'center',
    border: '1px solid',
    borderColor: '$primary-30',
  }}>
    {i}
  </Box>
))}
</Grid>

Typography

<Box $css={{
  fontSize: '14px',
  fontWeight: '600',
  fontFamily: 'var(--global-typography-font-sans)',
  lineHeight: '1.5',
  textAlign: 'center',
  textDecoration: 'underline',
  textTransform: 'uppercase',
  letterSpacing: '-0.02em',
  wordBreak: 'break-word',
  whiteSpace: 'nowrap',
  textOverflow: 'ellipsis',
  overflow: 'hidden',
}} />

Position

<Box $css={{
  position: 'absolute',
  top: '0',
  right: '0',
  bottom: '0',
  left: '0',
  inset: '0',           // top + right + bottom + left
  insetX: '16px',       // left + right
  insetY: '0',          // top + bottom
  zIndex: 10,
}} />

Visual

<Box $css={{
  opacity: 0.8,
  overflow: 'hidden',
  overflowX: 'auto',
  overflowY: 'scroll',
  cursor: 'pointer',
  pointerEvents: 'none',
  userSelect: 'none',
  visibility: 'hidden',
  objectFit: 'cover',
  objectPosition: 'center',
  aspectRatio: '16/9',
}} />

Transform & Transition

<Box $css={{
  transform: 'translateX(-50%)',
  transition: 'all 0.2s ease',
  animation: 'fadeIn 0.3s ease',
  scale: '1.05',
  rotate: '45deg',
  translate: '10px 20px',
}} />

색상 속성

색상 관련 속성은 인터랙티브 조건(hover/active/focus/disabled 등)을 지원합니다.

기본 색상 속성

<HStack $css={{ gap: '$spacing-300' }}>
<Box $css={{
  padding: '$spacing-400',
  bgColor: '$background-1',
  color: '$text-1',
  borderRadius: '$radius-200',
  border: '1px solid',
  borderColor: '$border-default',
}}>background-1</Box>
<Box $css={{
  padding: '$spacing-400',
  bgColor: '$background-2',
  color: '$text-2',
  borderRadius: '$radius-200',
}}>background-2</Box>
<Box $css={{
  padding: '$spacing-400',
  bgColor: '$primary-60',
  color: 'white',
  borderRadius: '$radius-200',
}}>primary-60</Box>
</HStack>

인터랙티브 조건

색상 속성에 객체를 전달하면 상태별 스타일이 적용됩니다. 아래 박스에 마우스를 올려보세요.

<Box $css={{
padding: '$spacing-400',
borderRadius: '$radius-200',
cursor: 'pointer',
transition: 'all 0.2s ease',
bgColor: {
  default: '$background-1',
  hover: '$primary-10',
  active: '$primary-20',
},
color: {
  default: '$text-1',
  hover: '$primary-60',
},
borderColor: {
  default: '$border-default',
  hover: '$primary-60',
},
border: '1px solid',
elevation: {
  default: '$elevation-2',
  hover: '$elevation-8',
},
}}>
Hover & Click me
</Box>

사용 가능한 조건

조건CSS 선택자설명
default기본 상태
hover&:hover마우스 호버
active&:active클릭 중
focus&:focus-visible키보드 포커스
disabled&:disabled비활성 상태
readOnly&:read-only읽기 전용
focusWithin&:focus-within자식 요소 포커스
groupHover[data-group]:hover &부모 그룹 호버

사용 가능한 토큰 색상

토큰 색상은 이름으로 직접 참조합니다:

글로벌 색상: primary-10primary-100, gray-5gray-90, red-10red-100, blue-10blue-100, green-10green-100, orange-10orange-100, yellow-10yellow-100, purple-10purple-100

시맨틱 색상: background-1background-4, border-default, border-1border-4, text-1text-6, support-error-1support-error-4, support-success-1~support-success-4

특수 값: transparent, currentColor, inherit

className / style 콜백

$css 외에 상태 기반 className과 style도 지원합니다.

<Button.Root
  $css={{ padding: '$spacing-400' }}
  className={(state) => state.disabled ? 'opacity-50' : ''}
  style={(state) => ({
    transform: state.loading ? 'scale(0.98)' : undefined,
  })}
>
  <Button.Text>Submit</Button.Text>
</Button.Root>

다른 스타일링과 함께 사용

$css prop은 기존 className, style과 함께 사용할 수 있습니다. 병합 우선순위는 다음과 같습니다:

  1. $css의 토큰 스타일 (가장 낮음)
  2. $css의 className
  3. 내부 컴포넌트 props
  4. 소비자의 className / style (가장 높음)
<Box
  $css={{ padding: '$spacing-400', bgColor: '$background-1' }}
  className="my-custom-class"
  style={{ border: '2px solid red' }}
>
  Content
</Box>