Skip to main content

Command Palette

Search for a command to run...

[ 살펴보기 ] RadixUI - Primitives

Published
6 min readView as Markdown
[ 살펴보기 ] RadixUI - Primitives
C

A developer living in Busan, Korea

RadixUI는 두 가지 group으로 나뉜다. 하나는 RadixUI primitives, 다른 하나는 RadixUI theme이다.

RadixUI primitives는 접근성과 관련된 aria, role tag가 적용된 unstyled component를 제공함으로서 개발하는 당사자가 원하는 style을 component에 적용한다. 반면 RadixUI theme은 RadixUI primitives을 기반으로 style이 적용된 component를 제공한다.

우선 RadixUI Primitives 사용법 부터 살펴보자. 해당 포스트에서 살펴본 예제는 Vite를 통해 설치한 React 프로젝트에서 tailwindcss를 통해 style을 적용한다. Vite를 통한 React 프로젝트 설정 방법과 tailwindcss 적용 방법은 아래 reference를 통해 확인할 수 있다.

Reference - Vite - getting started

Reference - Install Tailwind CSS with Vite

RadixUI는 MUI와 같은 UI library와는 달리 필요한 component마다 install하여 사용한다. 예를들어 RadixUI가 제공하는 avatar component를 사용하고자 한다면 avatar component를 설치해서 사용한다.

npm install @radix-ui/react-avatar

그리고 사용하고자 하는 component에 설치한 avatar component를 import하여 사용한다. 다시 언급하면 RadixUI primitives에서 제공하는 component의 style은 전반적으로 개발자의 몫이다.

import * as RadixAvatar from "@radix-ui/react-avatar";

const Avatar = () => {
  return (
    <RadixAvatar.Root className="block">
      <RadixAvatar.Image
        src="..."
        alt="Colm Tuite"
        className="w-20 h-20 rounded-full object-cover"
      />
      <RadixAvatar.Fallback className="flex justify-center items-center w-20 h-20 rounded-full object-cover border-2 border-orange-500">
        No Image
      </RadixAvatar.Fallback>
    </RadixAvatar.Root>
  );
};

export default Avatar;

이제 다음 component를 application에서 import하여 사용하면 다음과 같이 우리가 적용한 style대로 component가 render되는 것을 확인할 수 있다.

Fallback component는 render하는 image를 찾을 수 없을 때 사용된다. 만약 위의 예제에서 Image component에 잘못된 src property value를 전달하면 다음과 같이 Fallback의 children이 render된다.

Radix가 제공하는 component는 원래 html element에 설정할 수 있는 attribute와 더불어 추가적인 prop을 통해 Radix가 제공하는 component의 추가 기능을 설정할 수 있다. 예를들어 Image component의 onLoadingStatusChange prop을 통해 image의 load 여부를 detect할 수 있다.

import * as RadixAvatar from "@radix-ui/react-avatar";

const Avatar = () => {
  return (
    <RadixAvatar.Root className="block">
      <RadixAvatar.Image
        src="..."
        alt="Colm Tuite"
        className="w-20 h-20 rounded-full object-cover"
        onLoadingStatusChange={(status) => {
          if (status === "loaded") {
            console.log(" ::: image loaded ::: ");
          }
        }}
      />
      <RadixAvatar.Fallback className="flex justify-center items-center w-20 h-20 rounded-full object-cover border-2 border-orange-500">
        No Image
      </RadixAvatar.Fallback>
    </RadixAvatar.Root>
  );
};

export default Avatar;

Checkbox

이제 RadixUI가 제공하는 checkbox component를 살펴보자. checkbox를 사용하기 위해 component를 설치한다. 추가로 check 상태를 보여주기 위한 icon을 위해 icon component도 함께 설치해준다.

npm install @radix-ui/react-checkbox @radix-ui/react-icons

그리고 사용하고자 하는 component에 설치한 component를 import하여 사용한다. 마찬가지로 component의 style 적용은 개발자의 몫이다.

import * as RadixCheckbox from "@radix-ui/react-checkbox";
import { CheckIcon } from "@radix-ui/react-icons";

const CheckBox = () => (
  <div className="flex items-center space-x-3">
    <RadixCheckbox.Root
      defaultChecked
      id="signup-policy"
      className="flex justify-center items-center w-6 h-6 p-0 bg-white border-r-2"
    >
      <RadixCheckbox.Indicator className=" text-blue-500">
        <CheckIcon />
      </RadixCheckbox.Indicator>
    </RadixCheckbox.Root>
    <label className="Label" htmlFor="signup-policy">
      Accept terms and conditions.
    </label>
  </div>
);

export default CheckBox;

Checkbox Indecator component는 Checkbox Root component가 check된 상태일 때만 render된다. Checkbox에 state를 연동해 controlled component로 관리하고 싶을 때는 일반 checkbox에 state를 연동하듯이 state를 적용해준다.

import { CheckIcon } from "@radix-ui/react-icons";
import { useState } from "react";

const CheckBox = () => {
  const [checked, setChecked] = useState<RadixCheckbox.CheckedState>(true);
  return (
    <div className="flex items-center space-x-3">
      <RadixCheckbox.Root
        checked={checked}
        id="signup-policy"
        className="flex justify-center items-center w-6 h-6 p-0 bg-white border-r-2"
        onCheckedChange={(checkedState) => {
          setChecked(checkedState);
        }}
      >
        <RadixCheckbox.Indicator className=" text-blue-500">
          <CheckIcon />
        </RadixCheckbox.Indicator>
      </RadixCheckbox.Root>
      <label className="Label" htmlFor="signup-policy">
        Accept terms and conditions.
      </label>
    </div>
  );
};

export default CheckBox;

Radix가 제공하는 checkbox가 실제로 html tag render될 때 다음과 같이 role이나 aria tag가 목적과 상태에 맞게 설정되는 것을 확인할 수 있다. 또한 checkbox와 같이 특정한 상태가 존재하는 element는 data-state attribute에 현재 element의 상태 정보가 관리된다 ( react state와 무관한 checked, unchecked와 같은 element의 상태 )

<button type="button" 
        role="checkbox"
        aria-checked="true" 
        data-state="checked" 
        ...
 >
 ...
</button>

여기서 주의할 점은 위의 예제에서 볼 수 있듯이 Radix가 제공하는 checkbox는 input element가 아닌 button element로 render되기에 check 상태에 따라 다른 style을 적용하고 싶다면 input:checked와 같은 checked pseudo class 대신 data-state attribute를 사용할 수 있다.

Tailwind에서 data-state attribute를 통해 check 상태에 따라 다른 style을 적용하는 방법은 다음과 같다.

...
  return (
      <RadixCheckbox.Root
        checked={checked}
        id="signup-policy"
        className="flex justify-center items-center w-6 h-6 p-0 bg-white border-r-2 
                   data-[state='checked']:bg-green-300"
                   // data-state가 checked일 때 bg-green 적용
      >
        <RadixCheckbox.Indicator className=" text-blue-500">
          <CheckIcon />
        </RadixCheckbox.Indicator>
      </RadixCheckbox.Root>
  );
};
...

만약 plain css를 통해 style을 적용하고 있다면 다음과 같이 class name에 data-state 상태를 추가해서 checked일 때 style을 설정해준다.

.checkbox[data-state="checked"] {
    background-color:skyblue;
}

만약 tailwindcss가 아닌 emotion과 같은 CSS In JS library를 사용하고 있다면 radix component 기반으로 styled-component를 구성하여 사용할 수 있다.

우선 필요한 package를 설치한다.

npm install @emotion/react @emotion/styled

그리고 다음과 같이 styled-component를 구성하여 사용한다.

import styled from "@emotion/styled";
import * as RadixCheckbox from "@radix-ui/react-checkbox";
import { CheckIcon } from "@radix-ui/react-icons";
import { useState } from "react";

const StyledRadixCheckboxRoot = styled(RadixCheckbox.Root)`
  display: flex;
  justify-content: center;
  align-items: center;
  width: 20px;
  height: 20px;
  padding: 0;
  background-color: white;
  &[data-state="checked"] {
    background-color: skyblue;
  }
`;

const CheckBox = () => {
  const [checked, setChecked] = useState<RadixCheckbox.CheckedState>(true);
  return (
    <div className="flex items-center space-x-3">
      <StyledRadixCheckboxRoot>
        <RadixCheckbox.Indicator className=" text-blue-500">
          <CheckIcon />
        </RadixCheckbox.Indicator>
      </StyledRadixCheckboxRoot>
      <label className="Label" htmlFor="signup-policy">
        Accept terms and conditions.
      </label>
    </div>
  );
};

export default CheckBox;

Composition

만약 Radix component가 default로 render하는 html elemenet를 변경하고 싶다면 asChild prop을 Radix component에 전달해줄 수 있다. 예를 들어 Avatar fallback component는 default로 span element로 render된다. 만약 이를 div element로 변경하고 싶다면 다음과 같이 설정해준다.

import * as RadixAvatar from "@radix-ui/react-avatar";

const Avatar = () => {
  return (
    <RadixAvatar.Root className="block">
      ...
      <RadixAvatar.Fallback 
        asChild
        className="flex justify-center items-center w-20 h-20 rounded-full object-cover border-2 border-orange-500"
       >
          <div>No Image</div>
      </RadixAvatar.Fallback>
    </RadixAvatar.Root>
  );
};

export default Avatar;

위의 예제와 같이 Radix compoennt에 asChild prop을 추가하고 children에 원하는 element를 추가해주면 해당 element에 Radix component에 설정한 attribute가 전달된다.

Radix가 default로 제공하는 accessbility 지원 요소를 누리기 위해 왠만해선 Radix가 제공하는 default element를 그대로 사용하는 것이 좋다.

Accessbility를 신경쓰지 않아도 되는 사항이라면 무관하겠지만 그렇지 않다면 asChild prop을 통해 별도의 element를 사용할 때는 accessibility 지원 요소에 대한 추가적인 주의가 필요할 수도 있다.

만약 위의 예제와 같이 특정 element를 children으로 직접 사용하는 것이 아닌 React Component를 children으로 사용할 때는 다음 두 가지 사항이 지켜져야 한다.

  • Children element로 사용되는 Component는 전달 받는 props을 모두 spread해준다. Radix component가 asChild prop으로 인해 child component의 element로 render될 때 Radix component의 props과 events handlers가 child element로 전달되어야 하므로 하위 component가 전달 받는 모든 props을 spread해준다.

       import React, { PropsWithChildren } from "react";
    
      type Props = PropsWithChildren & React.HTMLAttributes<HTMLParagraphElement>;
    
      const Typography = ({ children, ...props }: Props) => {
        return <p {...props}>{children}</p>;
      };
    
      export default Typography;
    
  • Children element로 사용되는 Component는 Forward.ref를 통해 wrapping 되어야 한다. 해당 사항은 모든 경우에 해당하진 않지만 Radix compoennt가 ref prop을 하위 component에 전달하여 연동해야 하는 경우도 있으므로 child element로 사용되는 component는 forward.ref를 통해 wrapping 해두는 것이 좋다.

      import React, { PropsWithChildren } from "react";
    
      type Props = PropsWithChildren & React.HTMLAttributes<HTMLParagraphElement>;
    
      const Typography = React.forwardRef<HTMLParagraphElement, Props>(
        ({ children, ...props }, ref) => {
          return (
            <p ref={ref} {...props}>
              {children}
            </p>
          );
        }
      );
    
      export default Typography;
    

그리고 위와 같이 설정한 component를 Radix component의 child로 html element를 바로 전달해서 사용한 것과 같이 react component를 전달하여 사용할 수 있다.

import * as RadixAvatar from "@radix-ui/react-avatar";
import Typography from "./Typography";

const Avatar = () => {
  return (
    <RadixAvatar.Root className="block">
      ...
      <RadixAvatar.Fallback
        asChild
        className="flex justify-center items-center w-20 h-20 rounded-full object-cover border-2 border-orange-500"
      >
        <Typography>No Image</Typography> // component를 child로 사용
      </RadixAvatar.Fallback>
    </RadixAvatar.Root>
  );
};

export default Avatar;

Utilities

Radix는 기본 component외에 추가 utilities package를 제공한다. 전체 Utilities는 doccumentation에서 확인할 수 있으며 그 중 일부를 살펴보자. ( Reference - Utilities )

사용하는 icon에 accessibility 요소를 추가하고 싶다면 Radix에서 제공하는 react-accessible-icon package를 사용할 수 있다.

npm install @radix-ui/react-accessible-icon

그리고 사용하는 icon에 wrapping하여 label prop을 통해 icon의 용도를 명시한다. 다음과 같이 label property를 추가하면 실제 화면상으로 변화가 생기지는 않지만 screen reader를 통해 해당 icon을 읽을 때 label에 설정한 text가 읽힌다.

import * as AccessibleIcon from "@radix-ui/react-accessible-icon";
import { Cross1Icon } from "@radix-ui/react-icons";

...
return (
    <AccessibleIcon.Root label="cancel">
      <Cross1Icon />
    </AccessibleIcon.Root>
)
...

화면상으로는 보이지 않지만 screen reader와 같은 assistive 도구를 위한 정보를 기입하고 싶다면 react-visually-hidden utility package를 사용할 수 있다.

npm install @radix-ui/react-visually-hidden

위의 예제를 react-visually-hidden utility를 통해 구현하면 다음과 같다.

import * as VisuallyHidden from "@radix-ui/react-visually-hidden";
import { Cross1Icon } from "@radix-ui/react-icons";

...
return (
   <div>
     <Cross1Icon />
     <VisuallyHidden.Root>cancel</VisuallyHidden.Root>
   </div>
)
...

More from this blog

[ 살펴보기 ] TypeORM - Transactions, Migration

Transation Database 종류에 따라 detail한 부분은 차이점이 조금씩 있겠지만 각 sql statement는 개별적인 transaction block을 통해 실행되며 Database 설정에 따라 sql statement의 실행 결과가 자동으로 commit되어 영구히 적용되거나 commit을 직접 실행하기 전까지는 영구히 적용되지 않을 수 있다. 대부분의 경우 default로 sql statement 실행 결과가 자동으로 comm...

Feb 9, 20256 min read
[ 살펴보기 ] TypeORM - Transactions, Migration

Dev Diary

184 posts