doksam-ui
홈ProfilesTokensIconsComponentsPatternsTemplatesRules
Patterns

레이아웃 패턴

대시보드·목록·상세폼·탭·빈 페이지 등 페이지 단위 레이아웃 5종입니다.

#1

대시보드 레이아웃

요약 카드 + 차트 그리드로 구성하는 기본 대시보드 구조입니다.

대시보드 제목총 1,234건

매출

1,000건

주문

2,000건

사용자

3,000건

방문

4,000건

전환

5,000건

일별 추이

차트 영역

카테고리 분포

차트 영역
<div className="flex flex-col gap-3">
  <div className="flex items-center gap-2">
    <ChartBarIcon size={16} className="text-primary" />
    <span className="text-sm font-bold">대시보드 제목</span>
    <Badge variant="secondary">총 1,234건</Badge>
  </div>
  <div className="grid grid-cols-3 gap-1.5 md:grid-cols-5">
    {summaries.map((s) => (
      <Card key={s.label}>
        <CardContent className="px-2 py-2">
          <p className="text-[9px] text-muted-foreground">{s.label}</p>
          <span className="text-base font-black text-primary">{s.value}</span>
        </CardContent>
      </Card>
    ))}
  </div>
  <div className="grid grid-cols-1 gap-2 lg:grid-cols-2">
    {charts.map((c) => (
      <Card key={c.title}>
        <CardContent className="px-2 pt-2 pb-1">{/* 차트 */}</CardContent>
      </Card>
    ))}
  </div>
</div>
  • 최상위 컨테이너는 flex flex-col gap-3 로 세로 리듬을 통일한다.
  • 요약 카드 그리드는 grid-cols-3 md:grid-cols-5, 차트 그리드는 grid-cols-1 lg:grid-cols-2 가 기본이다.
  • 숫자는 toLocaleString() 으로 천단위 콤마를 넣는다.
#2

목록 페이지

고정 헤더 테이블 + ScrollArea + 페이징으로 구성하는 목록 화면입니다.

직원 관리전체 50건
#이름부서상태
1김철수개발팀재직
2이영희디자인팀재직
3박민수기획팀휴직
4정수진마케팅재직
5최동우인사팀재직
1-5 / 50건
<div className="flex flex-col flex-1 min-h-0 gap-2">
  <div className="flex items-center justify-between">
    <span className="text-sm font-bold">직원 관리</span>
    <Button size="sm" variant="outline">등록</Button>
  </div>
  <Card>
    <CardContent className="p-0">
      <ScrollArea className="h-[140px]">
        <Table>
          <TableHeader className="sticky top-0 z-10 bg-background">
            <TableRow>{/* ... */}</TableRow>
          </TableHeader>
          <TableBody>{/* ... */}</TableBody>
        </Table>
      </ScrollArea>
    </CardContent>
  </Card>
</div>
  • 페이지 루트는 flex flex-col flex-1 min-h-0 gap-2 로 남은 높이를 ScrollArea 에 위임한다.
  • TableHeader 는 sticky top-0 z-10 bg-background 로 스크롤 시 고정한다.
  • 행 상태는 Badge variant 로 구분(재직=default, 그 외=secondary)한다.
#3

상세/폼 페이지

2~4컬럼 폼 레이아웃으로 구성하는 등록/수정 화면입니다.

사용자 등록
입력값
입력값
입력값
입력값
<div className="rounded-md border bg-muted/30 p-4">
  <div className="grid grid-cols-2 gap-3 md:grid-cols-4">
    {fields.map((field) => (
      <div key={field.label} className="space-y-1">
        <Label className="text-[10px]">{field.label}</Label>
        <Input className="h-8 text-xs" />
      </div>
    ))}
  </div>
  <div className="mt-3 flex justify-end gap-2">
    <Button size="sm" variant="outline">취소</Button>
    <Button size="sm">저장</Button>
  </div>
</div>
  • 폼 영역은 rounded-md border p-4 bg-muted/30 으로 본문과 시각적으로 구분한다.
  • 필드 그리드는 grid grid-cols-2 md:grid-cols-4 gap-3 이 기본값이다.
  • 액션 버튼은 우측 정렬(justify-end)하고 취소를 먼저, 저장을 나중에 둔다.
#4

탭 페이지

여러 탭으로 화면을 분리하는 레이아웃 — 탭별로 파일을 분리해 구현합니다.

데이터 관리
수집 실행
결과
수집 데이터
탭 콘텐츠 영역 (탭마다 별도 파일로 분리)
<div className="flex gap-1 border-b border-border">
  {tabs.map((tab) => (
    <button
      key={tab.value}
      className={cn(
        "rounded-t border-b-2 px-3 py-1.5 text-xs font-medium",
        tab.active
          ? "border-primary bg-accent/50 text-foreground"
          : "border-transparent text-muted-foreground",
      )}
    >
      {tab.label}
    </button>
  ))}
</div>
  • shadcn Tabs 프리미티브(components/ui/tabs.tsx)로 접근성 있는 탭을 구성한다.
  • 탭이 많거나 각 탭 로직이 복잡하면 탭별로 별도 컴포넌트 파일로 분리한다.
  • 활성 탭은 border-primary bg-accent/50, 비활성은 border-transparent text-muted-foreground 로 구분한다.
#5

빈 페이지 템플릿

새 화면을 시작할 때 쓰는 최소 구조 스켈레톤입니다.

새 페이지
<div className="flex flex-col gap-3">
  <div className="space-y-1">
    <Skeleton className="h-8 w-[160px]" />
    <Skeleton className="h-4 w-[240px]" />
  </div>
  <div className="grid grid-cols-3 gap-1.5 md:grid-cols-5">
    {Array.from({ length: 5 }).map((_, i) => (
      <Card key={i}>
        <CardContent className="space-y-1 px-2 py-2">
          <Skeleton className="h-3 w-[40px]" />
          <Skeleton className="h-5 w-[60px]" />
        </CardContent>
      </Card>
    ))}
  </div>
</div>
  • 실제 페이지의 레이아웃 구조(제목·요약 카드·차트)를 그대로 미러링해 스켈레톤을 만든다.
  • 반복 요소는 Array.from({ length: N }).map() 으로 개수를 명시한다.
  • 이 구조를 그대로 app/<route>/loading.tsx 초안으로 활용할 수 있다.