콘텐츠로 건너뛰기

커맨드

⌘K 메뉴, 전체 검색 및 빠른 동작을 위한 키보드 기반 명령 팔레트 — 네오브루탈리즘 스타일의 테두리와 그림자 효과가 적용되었습니다.

import {
  Calculator,
  Calendar,

Command 컴포넌트는 조합 가능한 커맨드 팔레트입니다. 입력하는 대로 그룹으로 묶인 동작 목록을 필터링하는 입력 필드와, ⌘K 용도를 위한 CommandDialog 래퍼로 이루어집니다. cmdk를 기반으로 하며, 네오브루탈리즘 공식(두꺼운 테두리, 선명한 그림자, 굵은 서체)에 맞춰 스타일을 입혔습니다.

다음과 같은 경우에 사용합니다.

  • ⌘K 팔레트 — 전역 단축키에 CommandDialog를 연결해 앱 전체의 이동과 동작을 처리합니다.
  • 문서와 앱 검색 — 사용자가 입력하는 대로 페이지, 레코드, 설정을 카테고리별로 묶어 필터링합니다.
  • 옵션이 많은 선택기 — 담당자, 레이블, 이모지처럼 평범한 select로는 감당하기 어려운 경우.

개요

<Command /> 컴포넌트는 Dipcmdk 컴포넌트를 사용합니다.

설치

pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/command.json
npx shadcn@latest add https://neobrutalism.com/r/base/command.json
yarn dlx shadcn@latest add https://neobrutalism.com/r/base/command.json
bunx --bun shadcn@latest add https://neobrutalism.com/r/base/command.json

다음 의존성을 설치합니다.

pnpm add cmdk
npm install cmdk
yarn add cmdk
bun add cmdk

다음 코드를 복사해 프로젝트에 붙여넣습니다.

components/ui/command.tsx
"use client"

import * as React from "react"
import { Command as CommandPrimitive } from "cmdk"
import { CheckIcon, SearchIcon } from "lucide-react"

import { cn } from "@/lib/utils"
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog"
import { InputGroup, InputGroupAddon } from "@/components/ui/input-group"

function Command({
  className,
  ...props
}: React.ComponentProps<typeof CommandPrimitive>) {
  return (
    <CommandPrimitive
      data-slot="command"
      className={cn(
        "flex size-full flex-col overflow-hidden rounded border-2 bg-popover p-1 text-popover-foreground",
        className
      )}
      {...props}
    />
  )
}

function CommandDialog({
  title = "Command Palette",
  description = "Search for a command to run...",
  children,
  className,
  showCloseButton = false,
  ...props
}: React.ComponentProps<typeof Dialog> & {
  title?: string
  description?: string
  className?: string
  showCloseButton?: boolean
}) {
  return (
    <Dialog {...props}>
      <DialogHeader className="sr-only">
        <DialogTitle>{title}</DialogTitle>
        <DialogDescription>{description}</DialogDescription>
      </DialogHeader>
      <DialogContent
        className={cn(
          "top-1/3 translate-y-0 overflow-hidden rounded p-0",
          className
        )}
        showCloseButton={showCloseButton}
      >
        {children}
      </DialogContent>
    </Dialog>
  )
}

function CommandInput({
  className,
  ...props
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
  return (
    <div data-slot="command-input-wrapper" className="p-1 pb-0">
      <InputGroup className="h-8! rounded border-input/30 bg-input/30 shadow-none! *:data-[slot=input-group-addon]:pl-2!">
        <CommandPrimitive.Input
          data-slot="command-input"
          className={cn(
            "w-full text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
            className
          )}
          {...props}
        />
        <InputGroupAddon>
          <SearchIcon className="size-4 shrink-0 opacity-50" />
        </InputGroupAddon>
      </InputGroup>
    </div>
  )
}

function CommandList({
  className,
  ...props
}: React.ComponentProps<typeof CommandPrimitive.List>) {
  return (
    <CommandPrimitive.List
      data-slot="command-list"
      className={cn(
        "no-scrollbar max-h-72 scroll-py-1 overflow-x-hidden overflow-y-auto outline-none",
        className
      )}
      {...props}
    />
  )
}

function CommandEmpty({
  className,
  ...props
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
  return (
    <CommandPrimitive.Empty
      data-slot="command-empty"
      className={cn("py-6 text-center text-sm", className)}
      {...props}
    />
  )
}

function CommandGroup({
  className,
  ...props
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
  return (
    <CommandPrimitive.Group
      data-slot="command-group"
      className={cn(
        "overflow-hidden p-1 text-foreground **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground",
        className
      )}
      {...props}
    />
  )
}

function CommandSeparator({
  className,
  ...props
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
  return (
    <CommandPrimitive.Separator
      data-slot="command-separator"
      className={cn("-mx-1 h-px bg-border", className)}
      {...props}
    />
  )
}

function CommandItem({
  className,
  children,
  ...props
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
  return (
    <CommandPrimitive.Item
      data-slot="command-item"
      className={cn(
        "group/command-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none in-data-[slot=dialog-content]:rounded-sm! data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-selected:bg-accent data-selected:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-selected:*:[svg]:text-accent-foreground",
        className
      )}
      {...props}
    >
      {children}
      <CheckIcon className="ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100" />
    </CommandPrimitive.Item>
  )
}

function CommandShortcut({
  className,
  ...props
}: React.ComponentProps<"span">) {
  return (
    <span
      data-slot="command-shortcut"
      className={cn(
        "ml-auto text-xs tracking-widest text-muted-foreground group-data-selected/command-item:text-accent-foreground",
        className
      )}
      {...props}
    />
  )
}

export {
  Command,
  CommandDialog,
  CommandInput,
  CommandList,
  CommandEmpty,
  CommandGroup,
  CommandItem,
  CommandShortcut,
  CommandSeparator,
}

임포트 경로를 프로젝트 구성에 맞게 수정합니다.

사용법

import {
  Command,
  CommandDialog,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
  CommandSeparator,
  CommandShortcut,
} from "@/components/ui/command"
<Command className="max-w-sm rounded-lg border">
  <CommandInput placeholder="Type a command or search..." />
  <CommandList>
    <CommandEmpty>No results found.</CommandEmpty>
    <CommandGroup heading="Suggestions">
      <CommandItem>Calendar</CommandItem>
      <CommandItem>Search Emoji</CommandItem>
      <CommandItem>Calculator</CommandItem>
    </CommandGroup>
    <CommandSeparator />
    <CommandGroup heading="Settings">
      <CommandItem>Profile</CommandItem>
      <CommandItem>Billing</CommandItem>
      <CommandItem>Settings</CommandItem>
    </CommandGroup>
  </CommandList>
</Command>

구성

Command 를 구축하려면 다음 구성을 사용합니다.

Command
├── CommandInput
└── CommandList
    ├── CommandEmpty
    ├── CommandGroup
    │   ├── CommandItem
    │   └── CommandItem
    ├── CommandSeparator
    └── CommandGroup
        ├── CommandItem
        └── CommandItem

예제

기본

다이얼로그 안의 간단한 커맨드 메뉴입니다.

"use client"

import * as React from "react"

단축키

"use client"

import * as React from "react"

그룹

그룹, 아이콘, 구분선을 갖춘 커맨드 메뉴입니다.

"use client"

import * as React from "react"

스크롤

여러 항목을 포함한 스크롤 가능한 커맨드 메뉴입니다.

"use client"

import * as React from "react"

RTL

Neobrutalism에서 RTL 지원을 활성화하는 방법은 RTL 설정 가이드를 참고하세요.

"use client"

import * as React from "react"

접근성

커맨드 메뉴는 WAI-ARIA Combobox 패턴을 따릅니다. cmdk가 입력 요소를 combobox로 렌더링하고 aria-controlsaria-activedescendant로 옵션 listbox에 연결하므로, DOM 포커스는 입력 요소에 머무르면서도 스크린 리더는 강조된 항목을 읽어 줍니다. CommandDialog는 그 위에 모달 다이얼로그 시맨틱을 얹습니다.

키보드 상호작용:

동작
ArrowDown / ArrowUp다음/이전 항목으로 선택 이동
Home / End첫 번째/마지막 항목으로 이동
Enter선택한 항목 실행
Escape메뉴 닫기(CommandDialog로 렌더링한 경우)

API 참조

자세한 내용은 cmdk 문서를 참고하세요.