기록

asChild와 navigationMenuTriggerStyle 간단하게 정리

als982001 2025. 5. 10. 17:43

1. 개요

 공부를 하면서 shadcn을 이용해보았는데, 여기서 asChild와 navigationMenuTriggerStyle이라는 것을 알게 되었다. 이것들이 묘하게 기억에 남아서 간단하게 기록해보려고 한다.

 

2. asChild

 asChild는 Radix UI 기반 컴포넌트(예: NavigationMenuLink)에서 사용되는 특수 prop이다. shadcn/ui도 Radix UI를 기반으로 만들어져 이 prop을 지원한다고 한다. 이 prop이 하는 역할은 다음과 같다.

  • 기본적으로 Radix UI 컴포넌트는 <button> 또는 <a> 등 자체 태그로 렌더링되는데, asChild를 쓰면 내가 원하는 태그(<Link>, <div> 등)를 대신 쓰게 한다.
  • 내부적으로는 Slot이라는 개념을 활용한다고 한다.

 

2-1. asChild 예시

import { Link } from "react-router";

// 기타 생략

<Button variant="secondary">
    <Link to="/auth/login">Login</Link>
</Button>

 

 위의 Button은 로그인 버튼이다. Button 내에 Link가 존재한다. 즉, Link는 단순한 자식 요소로 렌더링되며 이는 개발자 도구를 통해서도 확인할 수 있다.

 

파란색 네모에서 확인할 수 있다.

 

<Button asChild variant="secondary">
    <Link to="/auth/login">Login</Link>
</Button>

 

위처럼 asChild를 추가할 경우, Button 컴포넌트는 스타일과 기능만 전달하고 최종 태그는 Link, 즉 a 태그가 된다. 여담으로, 이 코드에서 asChild를 적용한 이유는 <button><a> ... </a></button>은 HTML에서 유효하지 않은 구조이기에 적용하였다.

 

 

2-2. 예시에서의 asChild 용도

  • 태그 대체: Button을 a 태그로 렌더링하고 싶을 때 (Link 사용 등)
  • 접근성 보장: 불필요한 <button><a> 중첩 구조 방지
  • 스타일 재사용: Button 스타일은 그대로 유지하면서 Link로 라우팅 가능

 

3. navigationMenuTriggerStyle

navigationMenuTriggerStyle는 shadcn/ui에서 유틸리티 클래스를 생성해주는 함수이다. 이 함수는 NavigationMenuTrigger 같은 버튼 스타일을 텍스트 링크(<Link>)에 동일하게 적용하려고 만든 스타일 유틸 함수이다. 그리고 이는 Tailwind CSS 클래스를 반환한다. 이 함수는 메뉴 중 드롭 다운이 없는 단일 링크를 버튼처럼 보이게 하고 싶을 때, 혹은 스타일 일관성을 유지하려고 할 때 사용하면 좋다.

 

3-1. 예시

{menu.items ? (
    <>
      <Link to={menu.to}>
        <NavigationMenuTrigger>{menu.name}</NavigationMenuTrigger>
      </Link>
      <NavigationMenuContent>
        <ul className="grid w-[600px] font-light gap-3 p-4 grid-cols-2">
          {menu.items?.map((item) => {
            return (
              <NavigationMenuItem
                key={item.name}
                className={cn([
                  "select-none rounded-md transition-colors focus:bg-accent  hover:bg-accent",
                  (item.to === "/products/promote" ||
                    item.to === "/jobs/submit") &&
                    "col-span-2 bg-primary/10 hover:bg-primary/20 focus:bg-primary/20",
                ])}
              >
                <NavigationMenuLink asChild>
                  <Link
                    className="p-3 space-y-1 block leading-none no-underline outline-none"
                    to={item.to}
                  >
                    <span className="text-sm font-medium leading-none">
                      {item.name}
                    </span>
                    <p className="text-sm leading-snug text-muted-foreground">
                      {item.description}
                    </p>
                  </Link>
                </NavigationMenuLink>
              </NavigationMenuItem>
            );
          })}
        </ul>
      </NavigationMenuContent>
    </>
  ) : (
    <Link to={menu.to}>
      {menu.name}
    </Link>
  );
}

 

 이 코드는 menu 객체에 items 프로퍼티가 존재하는 경우, <NavigationMenuTrigger>와 <NavigationMenuContent>를 렌더링하고, 그렇지 않으면 <Link> 컴포넌트를 렌더링한다. 이 때, Link는 아래처럼 화면에 보인다.

 

 

IdeasGPT가 Link를 렌더링하고 있는데, 묘하게 다른 버튼과 디자인이 다르다. 

 

<Link className={navigationMenuTriggerStyle()} to={menu.to}>
    {menu.name}
</Link>

 

그리고 이 코드처럼 navigationMenuTriggerStyle 함수를 이용하면 다른 버튼과 디자인을 맞출 수가 있다.

 

 

const navigationMenuTriggerStyle = cva(
  "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=open]:text-accent-foreground data-[state=open]:bg-accent/50 data-[state=open]:hover:bg-accent data-[state=open]:focus:bg-accent"
)

 

사실 이 함수가 반환하는 문자열을 그냥 복사, 붙여넣기해도 동일하지만, 편의성, 재사용성, 가독성, 수정 편의성을 위해 이 함수를 이용한다.

 


참고 문서