reopt designreopt design
DocsExploreToolsPricingBuilder
시작하기
개요
시작하기
Next.js 설치
Private install
핵심 개념
아키텍처
컴포지션 패턴
접근성
키보드 패턴
스타일링
테마 시스템
고급 패턴
구축·운영
Skills
AI 연동
CLI (opt surface add)
의존 그래프
도구
Canvas 카탈로그
Theme Builder
Form Builder
템플릿
템플릿
릴리즈
릴리즈 노트
Oopt-ui
reopt designreopt design

AI 시대를 위한 디자인 시스템

  • 문서
  • 가격
  • 릴리즈 노트
  • GitHub
  • 서비스 약관
  • 개인정보처리방침

© 2026 reopt-ai. All rights reserved.

핵심 개념
  1. 문서
  2. /
  3. 핵심 개념
  4. /
  5. 고급 패턴

고급 패턴

opt-ui 컴포넌트의 고급 사용법, 커스텀 스타일링, 상태 관리 패턴을 설명합니다.

reopt design · 업데이트 2026년 6월 26일

DataTable 필터링

DataTable과 Input을 조합하여 실시간 필터링을 구현합니다. 컬럼별 커스텀 렌더링과 Badge를 활용한 상태 표시도 함께 적용합니다.

상품 목록

상품명카테고리가격재고상태
프리미엄 무선 키보드전자기기₩129,00045개판매중
에르고노믹 마우스전자기기₩89,000120개판매중
USB-C 허브 7포트액세서리₩59,0000개보관됨
모니터 스탠드가구₩45,00023개판매중
노트북 파우치 15인치액세서리₩35,00089개준비중
블루투스 스피커전자기기₩79,00015개판매중
code
import { DataTable, Input, Badge } from "@reopt-ai/opt-ui";

const [search, setSearch] = useState("");

// 필터링 로직
const filtered = products.filter(
  (p) => p.name.includes(search) || p.category.includes(search)
);

// 커스텀 컬럼 렌더링
const columns = [
  { id: "name", header: "상품명", accessor: "name" },
  {
    id: "status",
    header: "상태",
    accessor: (row) => (
      <Badge variant={statusVariant[row.status]}>
        {statusLabel[row.status]}
      </Badge>
    ),
  },
];

<Input value={search} onChange={(e) => setSearch(e.target.value)} />
<DataTable data={filtered} columns={columns} />

DashboardGrid 반응형

DashboardGrid의 columns prop을 동적으로 변경하여 반응형 레이아웃을 구현합니다. 기간 필터와 함께 데이터 변경도 시뮬레이션합니다.

컬럼:
기간:
↑↓←→ 키보드로 탐색
총 매출₩12.4M↑ +12.5%
주문 수1,234↑ +8.2%
신규 고객89↓ -3.1%
전환율3.2%↑ +0.4%
이탈률42%↑ -2.1%
평균 주문액₩89,000↑ +5.3%
code
import { DashboardGrid, Button } from "@reopt-ai/opt-ui";

const [columns, setColumns] = useState(3);

// 화면 크기에 따른 반응형
useEffect(() => {
  const handleResize = () => {
    if (window.innerWidth < 640) setColumns(1);
    else if (window.innerWidth < 1024) setColumns(2);
    else setColumns(3);
  };
  window.addEventListener("resize", handleResize);
  return () => window.removeEventListener("resize", handleResize);
}, []);

<DashboardGrid stats={stats} columns={columns} />

Toast 알림 시스템

ToastProvider로 앱을 감싸고, useToast hook으로 어디서든 알림을 표시합니다. 변형별 스타일과 지속 시간 커스텀이 가능합니다.

버튼을 클릭하면 화면 우측 상단에 토스트가 표시됩니다.

code
import { ToastProvider, useToast } from "@reopt-ai/opt-ui";

// 1. 앱 최상위에 Provider 배치
function App() {
  return (
    <ToastProvider position="top-right" defaultDuration={5000}>
      <MyApp />
    </ToastProvider>
  );
}

// 2. 컴포넌트에서 useToast 사용
function SaveButton() {
  const { addToast } = useToast();

  const handleSave = async () => {
    try {
      await saveData();
      addToast({
        variant: "success",
        title: "저장 완료",
        message: "변경사항이 저장되었습니다.",
      });
    } catch (error) {
      addToast({
        variant: "error",
        title: "오류",
        message: "저장에 실패했습니다.",
        duration: 8000, // 8초 (기본값 오버라이드)
      });
    }
  };

  return <Button onClick={handleSave}>저장</Button>;
}

Combobox 그룹화

ComboboxGroup과 ComboboxGroupLabel로 검색 결과를 카테고리별로 그룹화합니다. 커스텀 아이템 렌더링으로 풍부한 UI를 구현합니다.

code
import {
  ComboboxRoot, ComboboxInput, ComboboxPopover,
  ComboboxItem, ComboboxGroup, ComboboxGroupLabel,
} from "@reopt-ai/opt-ui";

// 부서별 그룹화
const grouped = users.reduce((acc, user) => {
  if (!acc[user.department]) acc[user.department] = [];
  acc[user.department].push(user);
  return acc;
}, {});

<ComboboxRoot setValue={setSearch} resetValueOnHide>
  <ComboboxInput placeholder="팀원 검색..." autoSelect />
  <ComboboxPopover>
    {Object.entries(grouped).map(([dept, users]) => (
      <ComboboxGroup key={dept}>
        <ComboboxGroupLabel>{dept}</ComboboxGroupLabel>
        {users.map((user) => (
          <ComboboxItem key={user.id} value={user.name}>
            <Avatar name={user.name} size="sm" />
            <span>{user.name}</span>
          </ComboboxItem>
        ))}
      </ComboboxGroup>
    ))}
  </ComboboxPopover>
</ComboboxRoot>

커스텀 스타일링

opt-ui 컴포넌트는 className prop으로 Tailwind 클래스를 추가할 수 있습니다. data-* 속성 셀렉터로 상태별 스타일도 적용 가능합니다.

code
// 1. className으로 직접 스타일 추가
<Button className="shadow-lg hover:shadow-xl">
  그림자 버튼
</Button>

// 2. data-* 속성으로 상태 스타일
<ComboboxItem
  className={cn(
    // 기본 스타일
    "px-3 py-2",
    // 활성 상태 (키보드 탐색)
    "data-[active-item]:bg-blue-50 data-[active-item]:text-blue-700",
    // 포커스 visible (Tab 키)
    "data-[focus-visible]:ring-2 data-[focus-visible]:ring-blue-600",
  )}
>

// 3. 조건부 스타일
<Badge
  variant={status === "active" ? "success" : "default"}
  className={status === "error" ? "animate-pulse" : ""}
>
  {statusLabel}
</Badge>

컴포지션 패턴

여러 컴포넌트를 조합하여 복잡한 UI를 구성합니다. Primitives → Blocks → Compositions 계층을 활용합니다.

code
// 예: 검색 + 테이블 + 페이지네이션 조합
function DataExplorer<T>({ data, columns }: Props<T>) {
  const [search, setSearch] = useState("");
  const [page, setPage] = useState(1);

  const filtered = useMemo(
    () => data.filter(/* 검색 로직 */),
    [data, search]
  );

  const paginated = useMemo(
    () => filtered.slice((page - 1) * 10, page * 10),
    [filtered, page]
  );

  return (
    <div className="space-y-4">
      <SearchCombobox
        items={data.map(extractSearchableText)}
        onSelect={setSearch}
      />
      <DataTable data={paginated} columns={columns} />
      <Pagination
        total={filtered.length}
        page={page}
        onChange={setPage}
      />
    </div>
  );
}

키보드 우선 설계

opt-ui의 모든 컴포넌트는 키보드 탐색을 우선 지원합니다. 주요 패턴과 확인 사항입니다.

패턴설명컴포넌트
Roving Tabindex그룹 내 Tab 1회, 방향키로 이동CompositeZone, Tabs, Menu, Toolbar
Focus Trap모달 내부에 포커스 가둠Dialog, CommandPalette
Focus Restore닫힐 때 이전 요소로 복귀Dialog, Menu, Select
Typeahead문자 입력으로 빠른 탐색Select, Combobox, Menu
Previous테마 시스템Compound Theme 아키텍처, 5개 프리셋 × light/dark 모드, CSS 변수 체계핵심 개념
테마 시스템 페이지로 이동
NextSkillsopt-ui 설치, 환경 진단, 컴포넌트 생성, repo 동기화, release 워크플로우를 정리합니다.구축·운영