Styling

Styling (스타일링)

Radix Themes에서 스타일링에 접근하는 방법이에요.

출처: 문서

소개 (Introduction)

Radix Themes에는 내장된 스타일링 시스템이 없어요. css나 sx prop도 없고, 내부적으로 스타일링 라이브러리를 사용하지 않아요. 내부적으로는 순수한 vanilla CSS로 만들어졌어요.

그래서 앱에 어떤 스타일링 기술을 선택하든 오버헤드가 없어요.

제공되는 것 (What you get)

Radix Themes의 컴포넌트는 비교적 폐쇄적이에요. 쉽게 덮어쓰기 어려운 스타일 묶음과 함께 제공되며, props와 테마 설정이 허용하는 범위 안에서만 커스터마이징할 수 있어요.

하지만 Radix Themes 컴포넌트를 구동하는 동일한 CSS 변수에도 접근할 수 있어요. 이 토큰들을 사용하면 원래 테마와 자연스럽게 어울리는 커스텀 컴포넌트를 만들 수 있어요. 토큰 시스템의 변경은 breaking change로 취급돼요.

특정 토큰에 대한 자세한 내용은 Theme section의 해당 가이드를 참고해 주세요.

Color system

ABCD

ABCDEFG

ABCDEFGHI

ABCDEFGHIJ

ABCDEFGHIJKL

A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine. I am so happy, my dear friend, so absorbed in the exquisite sense of mere tranquil existence, that I neglect my talents. I should be incapable of drawing a single stroke at the present moment; and yet I feel that I never was a greater artist than now. When, while the lovely valley teems with vapour around me, and the meridian sun strikes the upper surface of the impenetrable foliage of my trees, and but a few stray gleams steal into the inner sanctuary, I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies, then I feel the presence of the Almighty, who formed us in his own image, and the breath

Ambiguous voice of a heart which prefers kiwi bowls to a zephyr.

Typography examples

Shadow and radius examples

스타일 오버라이드 (Overriding styles)

단순한 스타일 오버라이드를 넘어서는 경우, 컴포넌트를 있는 그대로 사용하거나 같은 구성 블록으로 자신만의 버전을 만드는 걸 권장해요.

대부분 컴포넌트에 className과 style prop이 있지만, 스타일을 많이 덮어써야 한다면 다음 중 하나를 고려해 보는 게 좋아요.

  • 기존 props와 테마 설정으로 원하는 것을 달성해 보세요.
  • 기본 토큰 시스템을 조정해 디자인을 구현할 수 있는지 확인해 보세요.
  • Primitives와 Colors 같은 저수준 구성 블록으로 자신만의 컴포넌트를 만드세요.
  • Radix Themes가 프로젝트에 맞는지 다시 생각해 보세요.

Tailwind

Tailwind는 훌륭해요. 다만 Radix Themes와 함께 Tailwind를 쓰려면, Tailwind의 작업 방식이 복잡한 스타일을 즉석에서 만들고 때로는 컴포넌트 내부까지 거리낌 없이 파고들도록 유도할 수 있다는 점을 염두에 두세요.

Tailwind는 서로 다른 스타일링 패러다임이에요. props, 토큰, 그리고 공유된 구성 블록 위에 새 컴포넌트를 만드는 방식으로 커스터마이징하는 폐쇄형 컴포넌트 시스템의 개념과 잘 섞이지 않을 수 있어요.

커스텀 컴포넌트 (Custom components)

커스텀 컴포넌트를 만들어야 한다면 Radix Themes가 사용하는 것과 같은 구성 블록을 사용해 주세요.

  • 컴포넌트를 구동하는 Theme 토큰
  • 접근성 있고 스타일이 없는 컴포넌트 라이브러리인 Radix Primitives
  • 아름다운 웹사이트와 앱을 만드는 색상 시스템인 Radix Colors

Radix Themes가 어떻게 만들어졌는지 소스 코드를 자유롭게 탐색해 보세요.

흔한 문제 (Common issues)

z-index 충돌 (z-index conflicts)

기본적으로 포탈(portalled)된 Radix Themes 컴포넌트는 충돌 없이 어떤 순서로든 중첩하고 쌓을 수 있어요. 예를 들어 다이얼로그를 여는 팝오버를 열고, 그 안에서 다시 다른 팝오버를 열 수 있죠. 모두 열린 순서대로 서로 위에 쌓여요.

자신만의 컴포넌트를 만들 때는 다음 규칙을 사용해 z-index 충돌을 피하세요.

  • 드물게 auto, 0, -1 이외의 z-index 값을 사용하지 마세요.
  • 서로 위에 쌓여야 하는 요소들을 포탈 안에서 렌더링하세요.

주요 콘텐츠와 포탈된 콘텐츠는 루트 <Theme> 컴포넌트의 스타일이 만드는 스태킹 컨텍스트(stacking context)로 분리돼요. 덕분에 z-index를 신경 쓰지 않고도 포탈된 콘텐츠를 주요 콘텐츠 위에 쌓을 수 있어요.

Next.js import 순서 (Next.js import order)

Next.js 13.0부터 14.1까지는 app/**/layout.tsx에서 CSS 파일의 import 순서가 보장되지 않아요. 그래서 Radix Themes가 올바르게 작성했더라도 자신의 스타일을 덮어쓸 수 있어요.

import "@radix-ui/themes/styles.css";

import "./my-styles.css";

이 Next.js 이슈는 간헐적으로 나타났다 사라질 수 있고, 개발 또는 프로덕션에서만 발생할 수도 있어요.

우회 방법으로 postcss-import를 사용해 모든 CSS를 먼저 단일 파일로 합치고 그 파일만 layout에 import하는 방법이 있어요. 또는 page.tsx 파일에서 직접 스타일을 import해도 동작해요.

Tailwind base 스타일 (Tailwind base styles)

Tailwind v3부터 @tailwind 지시어로 생성되는 스타일은 원래 import 순서와 무관하게 보통 import된 CSS 뒤에 추가돼요. 특히 Tailwind의 button reset 스타일이 Radix Themes 버튼을 방해해 일부 버튼이 배경색 없이 렌더링될 수 있어요.

우회 방법:

  • @tailwind base를 사용하지 마세요
  • Tailwind와 Radix Themes에 별도의 CSS layers를 설정하세요
  • postcss-import를 설정하고 Radix Themes 스타일 전에 @import tailwindcss/base로 Tailwind base 스타일을 수동으로 import하세요. 예시 설정

포탈에서 스타일 누락 (Missing styles in portals)

Radix Themes 프로젝트에서 커스텀 포탈을 렌더링하면, 포탈은 자연스럽게 루트 <Theme> 컴포넌트 밖에 나타나므로 대부분의 테마 토큰과 스타일에 접근할 수 없어요. 이를 해결하려면 포탈 콘텐츠를 다른 <Theme>으로 감싸면 돼요.

// Implementation example of a custom dialog using the low-level Dialog primitive

// Refer to https://www.radix-ui.com/primitives/docs/components/dialog

import { Dialog } from "radix-ui";

import { Theme } from "@radix-ui/themes";

function MyCustomDialog() {
  return (
    <Dialog.Root>
      <Dialog.Trigger>Open</Dialog.Trigger>

      <Dialog.Portal>
        <Theme>
          <Dialog.Overlay />

          <Dialog.Content>
            <Dialog.Title />

            <Dialog.Description />

            <Dialog.Close />
          </Dialog.Content>
        </Theme>
      </Dialog.Portal>
    </Dialog.Root>
  );
}

Radix Themes의 Dialog, Popover 같은 컴포넌트는 이미 이 처리를 해 주기 때문에, 자신만의 포탈 컴포넌트를 만들 때만 필요해요.

복잡한 CSS 우선순위 (Complex CSS precedence)

보통은 커스텀 CSS가 Radix Themes 스타일을 덮어쓰길 원할 거예요. 하지만 그 반대가 자연스러운 경우도 있어요.

브라우저 기본 margin만 재설정하는 간단한 문단 스타일을 생각해 보세요.

.my-paragraph {
  margin: 0;
}

asChild를 사용해 Box의 margin prop을 커스텀 문단에 적용할 수도 있어요.

import "@radix-ui/themes/styles.css";

import "./my-styles.css";

function MyApp() {
  return (
    <Theme>
      <Box asChild m="5">
        <p className="my-paragraph">My custom paragraph</p>
      </Box>
    </Theme>
  );
}

하지만 이건 직관적으로 동작하지 않아요. 커스텀 스타일이 Radix Themes 스타일 뒤에 import되므로 margin prop을 덮어써 버리거든요. 우회 방법으로 Radix Themes는 원래 styles.css가 기반으로 하는 별도의 tokens.css, components.css, utilities.css 파일을 제공해요.

import "@radix-ui/themes/tokens.css";

import "@radix-ui/themes/components.css";

import "@radix-ui/themes/utilities.css";

커스텀 스타일 뒤에 utilities.css를 import하면 레이아웃 props가 커스텀 스타일과 함께 예상대로 동작하도록 보장할 수 있어요. 다만 Next.js를 쓴다면 위에서 언급한 import 순서 이슈를 염두에 두세요.

단독 레이아웃 컴포넌트를 사용한다면, 분리된 CSS 파일도 제공돼요.

import "@radix-ui/themes/layout/tokens.css";

import "@radix-ui/themes/layout/components.css";

import "@radix-ui/themes/layout/utilities.css";

더 알아보기 (Learn more)

  • 포탈을 쓰는 커스텀 컴포넌트는 다시 <Theme>으로 감싸서 테마 토큰을 유지해야 해요.