본문으로 건너뛰기
컴포넌트

검색 트리거

아이콘, 단어, 단축키 칩이 한 버튼에 담긴 검색 입구예요.

  • #arc
  • #component
  • #search
  • #shortcut
  • #command-palette
  • #header

설치

이 명령 한 줄이면 필요한 파일과 패키지가 함께 설치돼요.

터미널bash
npx shadcn@latest add https://dsui.vercel.app/r/search-trigger.json

search-trigger.module.csscss
/* The trigger opens a floating layer, so it never scales on press: Radix measures the anchor on pointerdown. */
.trigger { display: inline-flex; min-height: var(--control-height-md); align-items: center; gap: var(--space-2); border: 1px solid var(--border); border-radius: var(--radius-control); padding: 0 var(--space-3) 0 var(--space-4); background: var(--surface); color: var(--text-secondary); font: inherit; font-size: var(--label-md); font-weight: var(--label-weight); line-height: var(--leading-body); white-space: nowrap; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: color var(--duration-fast) var(--ease-standard), background var(--duration-fast) var(--ease-standard), border-color var(--duration-fast) var(--ease-standard); }
.trigger svg { flex: none; }
.trigger:disabled { background: var(--surface-muted); border-color: transparent; color: color-mix(in oklab, var(--foreground) 55%, transparent); cursor: not-allowed; }
.label { color: inherit; }
/* A key hint, not a control: it keeps the quiet well so it never competes with the word beside it. */
.kbd { display: inline-flex; min-width: var(--space-6); height: var(--space-6); align-items: center; justify-content: center; border-radius: var(--radius-pill); padding: 0 var(--space-2); background: var(--surface-muted); color: var(--foreground); font-family: inherit; font-size: var(--text-xs); font-weight: var(--label-weight); }
.sm { min-height: var(--control-height-sm); padding-inline: var(--space-2) var(--space-3); font-size: var(--label-sm); }
.compact { padding-inline: 0; justify-content: center; width: var(--control-height-md); }
.sm.compact { width: var(--control-height-sm); }
@media (hover: hover) and (pointer: fine) {
  .trigger:hover:not(:disabled) { border-color: var(--foreground); color: var(--foreground); }
}
.trigger:active:not(:disabled) { background: var(--surface-muted); border-color: var(--foreground); color: var(--foreground); }
/* Pointers without hover never see the shortcut, and the chip only tells a keyboard user something they can act on. */
@media (hover: none) { .kbd { display: none; } }
@media (prefers-reduced-motion: reduce) { .trigger { transition: none; } }
search-trigger.tsxtsx
"use client";

import { forwardRef } from "react";
import type { ButtonHTMLAttributes } from "react";
import { playUiSound } from "../sound/play";
import { UiIcon } from "../theme/ui-icon";
import styles from "./search-trigger.module.css";

export interface SearchTriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  /** The word on the control, and its accessible name when the label is hidden. Default "검색". */
  label?: string;
  /** The shortcut chip, e.g. "⌘K". Left out, no chip shows. */
  shortcut?: string;
  /** Keeps the icon only. The label stays the accessible name. */
  compact?: boolean;
  size?: "sm" | "md";
}

/**
 * One control for the whole search entry point: icon, word and shortcut chip. It opens a dialog (a command palette),
 * so it plays the open sound and says so with aria-haspopup; the chip is hidden from assistive tech because
 * aria-keyshortcuts already carries the shortcut.
 */
export const SearchTrigger = forwardRef<HTMLButtonElement, SearchTriggerProps>(function SearchTrigger(
  { className, label = "검색", shortcut, compact = false, size = "md", onClick, ...props },
  ref,
) {
  const classes = [styles.trigger, styles[size], compact ? styles.compact : "", className].filter(Boolean).join(" ");
  return (
    <button
      {...props}
      ref={ref}
      type="button"
      className={classes}
      aria-label={compact ? label : props["aria-label"]}
      aria-haspopup="dialog"
      aria-keyshortcuts={props["aria-keyshortcuts"] ?? (shortcut ? "Meta+K Control+K" : undefined)}
      onClick={event => { playUiSound("open"); onClick?.(event); }}
    >
      <UiIcon iconRole="search" size={17} strokeWidth={1.75} aria-hidden="true" />
      {compact ? null : <span className={styles.label}>{label}</span>}
      {compact || !shortcut ? null : <kbd className={styles.kbd} aria-hidden="true">{shortcut}</kbd>}
    </button>
  );
});

SearchTrigger.displayName = "SearchTrigger";

export default SearchTrigger;