Tiro
Tiro Design
Guidelines

Composition

Primitive → Composite → Screen 조합 규칙.

원칙

  1. Primitive → Composite → Screen. Screen은 조립만. 인라인 primitive 생성 금지.
  2. 새 공유 UI는 먼저 components/ui/* (web) / src/components/ui/* (mobile) 에 랜딩, 이후 Screen이 소비.
  3. Platform delta는 명시적으로 선언 — 암묵적 분기 금지.
의도컴포넌트
정보·편집Dialog
복구 불가 확인AlertDialog
측면/하단 패널Sheet (web / mobile) / collapsible panel (desktop)
Non-modal 플로팅Popover
시스템 알림Toast

Form Field 규칙

모든 폼 필드 = Label + input + (helperText | errorText). 예외 없음.

<div>
  <Label htmlFor="email" required>이메일</Label>
  <Input id="email" invalid={!!error} />
  {error ? (
    <span className="caption1 text-destructive">{error}</span>
  ) : (
    <span className="caption1 text-muted">로그인에 사용됩니다</span>
  )}
</div>

State Prop Naming

표준 이름만 사용: default · pressed · disabled · loading · error.

금지: isBusy, waiting, inactive, pending.

한 브랜드 액센트

브랜드 액센트 컬러는 brown-800 하나. 인터랙티브 요소에 두 번째 액센트 도입 금지.

No Gradients

그라디언트 사용 금지. 솔리드 컬러만. (Rebrand에서 모든 그라디언트 제거됨.)

Light Mode Only

다크 테마 금지. 유일한 다크 표면은 브랜드 히어로 (brown-800 + white 텍스트).

Logo 사용 원칙

로고는 항상 둥근 사각형 컨테이너 안에 — Tiro가 "말하는" 순간(AI 채팅, 온보딩)만 예외.

Font 경계

Pretendard JP로 모든 UI. Averia는 브랜드 모멘트에만. 절대 교차 금지.

Raw Values 금지

  • raw hex 금지 — var(--*) 또는 semantic Tailwind 토큰만
  • raw px 금지 — 1px border, media query 예외
  • 모두 lint로 강제 (no-raw-hex, no-raw-px, no-slate-zinc, no-averia-in-ui)

실전 조합 예시

SurfaceComposition
로그인 페이지 (web)Card + Avatar(logo) + Typography.Averia + Button(oauth)×3 + Separator + Input + Label + Button(primary)
노트 리스트 (web)Typography.H3 + Button(secondary) + ListItem + Avatar + Badge + Separator
NoteListPage/NoteItem (web)Card + Badge + IconButton + DropdownMenu + Tooltip
DeleteAccountModal (web)AlertDialog + Input + Button
OrganizationMembers (web)Table + Avatar + Badge + DropdownMenu + Dialog
AI 채팅 패널 (desktop right)Avatar(logo) + Typography.Averia(greeting) + SuggestionPill×N + Input + IconButton
녹음 시작 패널 (mobile)Sheet(bottom) + Tabs + LanguagePair + Button(primary)
마케팅 히어로Typography.Averia(display) + Avatar(logo) + Photography
ParticipantsTab (mobile)TopNavigation + SearchField + ListItem + Avatar + Chip + IconButton + EmptyState
NoteSummarySection (mobile)TopNavigation + Card + Text + AIStreamingText + DropdownMenu
ParagraphSection (mobile)TranscriptBlock + SpeakerChip + Tooltip + AudioPlayer

Mobile Primitive Conventions

모든 모바일(RN) primitive와 그 사용 코드는 다음 규칙을 공유한다. 각 컴포넌트의 Usage 섹션은 이 규칙을 전제하고, 컴포넌트 고유의 props·variants만 다룬다.

1. NativeWind 스타일링

className=만 사용. inline style={} 객체 금지. 단, prop으로 받은 동적 색·transform 등 className으로 표현 불가능한 경우만 예외.

// ❌ Don't
<TouchableOpacity style={{ padding: 16, backgroundColor: '#3A2018' }}>

// ✓ Do
<TouchableOpacity className="p-[16px] bg-brown-800">

2. px 단위 명시

Tailwind shorthand(p-4)는 사용하지 않고 항상 [Npx] 표기. 디자인 토큰의 수치(spacing.4 = 16px)와 코드 표기가 1:1 일치해야 grep·치환이 안전함.

// ❌ Don't
className="p-4 gap-2 rounded-lg"

// ✓ Do
className="p-[16px] gap-[8px] rounded-[8px]"

3. 고정 width·height 금지

h-[44px], w-[200px] 같은 고정 차원 대신 padding·flex·gap·line-height의 합으로 최종 크기가 자연스럽게 결정되도록 작성.

// ❌ Don't
<TouchableOpacity className="h-[44px] w-full">

// ✓ Do — line-height(24) + py(10×2) = 44pt 자동
<TouchableOpacity className="px-[20px] py-[10px] self-stretch">

icon-only 정사각형도 fixed-size 대신 padding만으로:

// ✓ icon button 정사각형
<TouchableOpacity className="p-[10px]">

4. 터치 컴포넌트: TouchableOpacity

Pressable은 사용하지 않는다. TouchableOpacity activeOpacity={0.7}이 Tiro mobile 표준.

5. RN 빌트인 우선

TouchableOpacity·Text·View·TextInput·Image·Switch·ActivityIndicator 등 RN 빌트인으로 표현 가능하면 외부 의존성 없이 작성. 빌트인으로 처리 불가능한 케이스만 검증된 라이브러리 사용:

케이스라이브러리
BottomSheet 프레젠테이션@lodev09/react-native-true-sheet
Toastreact-native-toast-message
Swipe gesturereact-native-gesture-handler
가속 애니메이션react-native-reanimated
Pull-to-refreshRN 빌트인 RefreshControl

6. Primitive는 prop + children API

Web primitive와 동일한 시그니처를 유지해 양 플랫폼 consumer 경험이 같도록 한다.

// ✓ Mobile Button — 사용감이 web과 동일
<Button variant="ghost" size="icon" onPress={dismiss}>
  <X size={20} strokeWidth={1.75} />
</Button>

String children 자동 wrap 규칙: RN에서 텍스트는 반드시 <Text> 안에 있어야 함. 모든 primitive는 children이 string이면 컴포넌트 시맨틱에 맞는 typography 토큰을 자동 적용한 <Text>로 wrap한다. consumer가 <Button>기록 시작</Button>처럼 web과 같은 모양으로 작성할 수 있게 함.

const content =
  typeof children === 'string'
    ? <Text className={Typography.button1}>{children}</Text>
    : children

7. 확장은 React composition

기존 primitive를 wrap·children으로 합성. 같은 스타일 로직을 새 컴포넌트에 중복 작성 금지.

// ✓ IconButton은 Button 위에 얇은 래핑
export const IconButton = ({ icon, ...rest }: IconButtonProps) => (
  <Button size="icon" {...rest}>
    {icon}
  </Button>
)

8. 파일 위치

  • Primitive 파일: src/components/ui/* (1회 작성)
  • Screen에서는 import해서 조립만. Screen 내부에 inline styling 금지

9. Token import 경로

import { DesignColors } from 'constants/designTokens/colors'      // stone scale 정합 진행 중
import { Typography } from 'constants/designTokens/typography'    // button1, subtitle1 등 semantic 명명

새 색·간격을 직접 hex/숫자로 작성하지 말고 반드시 토큰 경로를 거친다.

10. Platform delta 명시

같은 컴포넌트의 web/mobile 구현이 분기되면 컴포넌트 .mdx의 Spec/Sizes 표에 Value (web) / Value (mobile) 컬럼으로 명시. 코드만으로 분기를 추론하게 두지 말 것.


Do / Don't

  • Do: Primitive → Composite → Screen 계층 엄수
  • Do: Platform delta 명시 선언
  • Do: shadcn default 우선, 필요한 override만
  • Don't: Screen 안에서 인라인 primitive 생성 금지
  • Don't: 두 번째 브랜드 액센트 도입 금지
  • Don't: 다크 테마, 그라디언트, raw hex/px 사용 금지

On this page