이 예제는 tamagui와 @tamagui/config의 버전을 2.7.7로 고정하고 @tamagui/config/v5에서 defaultConfig를 가져옵니다. 구성 이름은 v5이며 npm 패키지의 메이저 버전이 아닙니다. 이 두 숫자를 혼동하면 잘못된 설치 명령을 안내하게 됩니다. createTamagui는 컴포넌트 바깥에서 한 번 실행하고, 생성된 CSS을 보드 렌더링 전에 삽입하여 테마 변수와 기본 스타일을 실제 웹 환경에서 사용할 수 있도록 합니다.
소규모 구성 재정의를 명시적으로 표현
제공된 소스에서는 이 예제가 padding, backgroundColor처럼 읽기 쉬운 긴 형식의 속성을 사용하므로 onlyAllowShorthands를 false로 설정합니다. 이는 프리셋에 추가된 애플리케이션 수준의 선택입니다. v5 기본 구성에는 애니메이션 드라이버가 포함되어 있지 않으며, 이 예제에서는 드라이버를 새로 만들거나 애니메이션 전환을 지원한다고 주장하지 않습니다. 간단한 진행률 및 체크박스 동작은 별도의 애니메이션 의존성을 추가하지 않아도 작동합니다.
Tamagui 디자인 시스템 크기, 간격 및 모서리 반경 토큰
라벨이 같다고 토큰을 혼동하지 마세요
고정된 v5 구성에서 size $4는 44, space $4는 18, radius 4는 9입니다. 따라서 컴포넌트 크기, 레이아웃 간격, 모서리 반경에는 동일한 배율 규칙을 적용할 수 없습니다. 참고 자료에는 각 범주가 따로 나와 있으며, 예제에서는 실제 Tamagui 프롭을 통해 범주에 맞는 값을 사용합니다. 이 구성에서 사용하지 않는 일반적인 4px 단위 값의 목록보다 훨씬 유용합니다.
size.$4 = 44
space.$4 = 18
radius.4 = 9
본문 글꼴 $4 = 15px, 행간 23px
타이포그래피 스케일도 별도로 유지하세요
본문 글꼴 $4는 23px 행간의 15px이며, 컴포넌트 크기 $4는 44입니다. 시스템 글꼴 스택은 v5에서 제공되므로 글꼴 파일을 다운로드할 필요가 없습니다. H1, H2, Text에는 각각 기본 글꼴 컨텍스트가 적용되고, 소스에 지정된 fontSize와 lineHeight 값이 원본 보드의 계층 구조를 결정합니다. 바깥쪽 너비 880px은 이 레이아웃에만 적용되는 선택이며, 전역 Tamagui 토큰이 아닙니다.
Tamagui 디자인 시스템 시맨틱 테마와 외관
파란색 하위 테마와 밝은 모드 또는 어두운 모드 조합하기
TamaguiProvider에는 선택한 defaultTheme을 전달하고, Theme name="blue"는 이에 해당하는 하위 테마를 적용합니다. 소스에서는 컴포넌트 트리 곳곳에 16진수 색상 값을 반복해서 쓰지 않고 background, backgroundHover, color, borderColor 역할을 사용합니다. 표에는 실제 light_blue와 dark_blue 값이 기록되어 있으므로, 하나의 고정된 브랜드 팔레트가 적용된다고 가정하는 대신 구성에 따라 해석된 값을 비교할 수 있습니다.
외관을 바꿔도 보드 상태 유지하기
dark 플래그는 행, 입력 초안, 필터와 별개로 관리됩니다. 외관을 전환하면 기본 컴포넌트의 모양만 바뀌고 보드는 그대로 유지됩니다. 버튼과 체크박스에는 각자의 컴포넌트 하위 테마 값이 적용될 수 있으므로, 계산된 색상이 부모 배경 토큰과 같지 않을 수 있습니다. 계층 구조를 사용자 지정 CSS로 단순화하기 전에 렌더링된 상태를 확인하세요.
Tamagui 디자인 시스템 체크박스와 계산된 진행률
완료 상태는 하나의 행 컬렉션에서 관리하세요
각 행에는 고유 ID, 읽기 쉬운 텍스트, done 불리언 값이 있습니다. Checkbox에는 checked와 onCheckedChange를 전달하며, 값이 true일 때만 완료로 표시합니다. Checkbox.Indicator는 체크 표시를 제공합니다. 인접한 텍스트와 접근성 이름은 같은 습관을 가리키며, 취소선은 완료 상태를 시각적으로도 나타냅니다. 소스에서는 행과 동기화되지 않을 수 있는 별도의 완료 개수를 만들지 않습니다.
보드 전체를 기준으로 진행률 계산하기
Tamagui 디자인 시스템 초안 유효성 검사와 중복 항목 처리
실제로 제어되는 입력 만들기
Input은 value와 onChangeText를 통해 현재 초안을 관리합니다. Add habit 동작은 입력값의 앞뒤 공백을 제거한 뒤, 비어 있으면 화면에 오류 메시지를 표시하고 aria-invalid 및 오류 설명을 설정합니다. 입력이 비어 제출되면 포커스가 입력란으로 돌아갑니다. 대소문자를 구분하지 않는 중복 검사를 통해 같은 텍스트가 두 번째 행으로 추가되지 않도록 하며, 수정할 수 있도록 초안을 그대로 유지합니다. 이러한 규칙은 라이브러리가 아니라 원본 보드에 속합니다.
유효한 항목을 추가한 뒤에만 보드를 업데이트하세요
초안이 유효하면 미완료 상태의 새 행을 추가하고, 입력을 비우고, 오류를 제거합니다. 새 컬렉션을 기준으로 계산되므로 진행률의 분모도 즉시 바뀝니다. 습관을 추가해도 알림이 등록되거나 계정이 생성되지는 않습니다. 보드 상태는 의도적으로 로컬 React 상태로 유지되며, 문서를 새로 불러오면 예시 상태로 돌아갑니다.
Tamagui 디자인 시스템 필터링된 보기와 빈 상태 복구
필터가 적용되는 범위를 명확히 하세요
미완료 항목만 표시하면 완료된 행은 컬렉션에 남겨 두고 화면에서만 숨깁니다. 해당 보기에서 표시할 항목이 없으면, 전체 목록은 그대로 있다는 설명과 전체 목록으로 돌아가는 바로가기 동작을 빈 상태에 표시합니다. 이는 사용자의 선택에 따라 의미 있게 나타나는 빈 상태이지, 임의의 플레이스홀더 카드를 바꾸는 장식용 토글이 아닙니다.
초기화와 외관 변경을 구분하세요
예시 보드 초기화는 처음의 세 행을 복원하고, 초안과 오류를 지우며, 전체 목록 보기로 돌아갑니다. 현재 외관은 바꾸지 않습니다. 각 변경이 명시적인 상태로 관리되므로 문서를 새로 불러오거나 설정된 provider를 버릴 필요가 없습니다. 테마와 콘텐츠 복구를 각각 독립적으로 테스트할 수 있습니다.
Tamagui 디자인 시스템 반응형 스택과 접근성 있는 읽기 흐름
스택 높이는 콘텐츠에 따라 결정되도록 하세요
YStack은 주요 읽기 흐름을 배치하고, XStack은 제목 컨트롤, 체크박스 행, 작업 버튼을 정렬합니다. 컨트롤 그룹은 줄바꿈되므로 데스크톱 너비를 고정할 필요가 없습니다. 바깥쪽 컨테이너의 최대 너비는 880px이며, 패딩에는 실제 space $4 토큰을 사용합니다. 각 행에서는 텍스트를 축소하고 줄바꿈할 수 있으며, 컨트롤은 280px 너비와 그보다 넓은 테스트 너비에서 모두 사용할 수 있습니다.
레이아웃이 바뀐 뒤에도 상호작용을 테스트하세요
브라우저 테스트에서는 오버플로를 측정하기 전에 습관을 추가하고, 완료로 표시하고, 목록을 필터링하고, 외관을 전환합니다. 처음 레이아웃의 스크린샷만으로는 오류, 긴 라벨, 필터링된 상태가 좁은 화면 구성에서 문제를 일으키는지 확인할 수 없습니다. 기본 체크박스의 시맨틱과 표시되는 텍스트는 진행률 계산과 함께 실제로 테스트했으며, 단순히 존재한다고 가정하지 않았습니다.
Tamagui 디자인 시스템 브라우저 빌드 완성하기
provider, CSS, 빌드 정의를 제공하세요
제공된 세 파일이면 로컬 웹 예제를 실행하기에 충분합니다. JSX에는 provider, 설정 하나, 생성된 테마 CSS, 전체 보드가 포함되어 있습니다. build.mjs는 웹 대상과 프로덕션 환경을 고정하고, 브라우저 전용 환경 변수 참조에 process.env가 없을 때 사용할 빈 값을 제공합니다. 이 마지막 정의가 없으면 컴파일에 성공하더라도 process is not defined 오류로 일반 번들 실행이 실패할 수 있습니다.
tamagui.jsx, build.mjs, index.html을 같은 위치에 저장하세요.
고정된 React 및 Tamagui 패키지를 설치하세요.
로컬 디렉터리를 빌드하고 제공하세요.
빈 항목 및 중복 항목 추가, 완료 처리, 필터, 테마, 초기화를 테스트하세요.
네이티브와 웹 도입을 별도로 다루세요
Tamagui 디자인 시스템 컴포넌트 예제
tamagui.jsx
설정, 생성된 CSS, 네이티브 컴포넌트, 전체 보드 상태
jsx
import React,{useState} from 'react';import{createRoot}from'react-dom/client';
import{createTamagui,TamaguiProvider,Theme,YStack,XStack,Text,H1,H2,Button,Input,Checkbox,Progress,Separator}from'tamagui';import{defaultConfig}from'@tamagui/config/v5';
const config=createTamagui({...defaultConfig,settings:{...defaultConfig.settings,onlyAllowShorthands:false}});const sheet=document.createElement('style');sheet.textContent=config.getCSS();document.head.append(sheet);
const initial=[{id:1,text:'Read ten pages',done:false},{id:2,text:'Step outside for a break',done:true},{id:3,text:'Write one useful note',done:false}];
function App(){const[dark,setDark]=useState(false),[rows,setRows]=useState(initial),[draft,setDraft]=useState(''),[error,setError]=useState(''),[filter,setFilter]=useState(false);const done=rows.filter(x=>x.done).length;let shown=rows.filter(x=>!filter||!x.done);function add(){let text=draft.trim();if(!text){setError('Write a small habit before adding it.');document.querySelector('#habit-input')?.focus();return}if(rows.some(x=>x.text.toLowerCase()===text.toLowerCase())){setError('That habit is already in this list.');return}setRows([...rows,{id:Date.now(),text,done:false}]);setDraft('');setError('')}return <TamaguiProvider config={config} defaultTheme={dark?'dark':'light'}><Theme name="blue"><YStack backgroundColor="$background" color="$color" padding="$4" gap="$4" maxWidth={880} margin="auto" width="100%" minHeight={0}><XStack justifyContent="space-between" alignItems="center" flexWrap="wrap" gap="$3"><Text fontSize="$2" fontWeight="700">TAMAGUI / ORIGINAL HABIT BOARD</Text><Button size="$3" onPress={()=>setDark(!dark)}>{dark?'Light appearance':'Dark appearance'}</Button></XStack><H1 fontSize="$8" lineHeight="$8">A little progress, every day.</H1><Text fontSize="$4" lineHeight="$5">A local board built with Tamagui 2.7.7 and its v5 blue theme. Your changes stay in this page; no account or daily reminder is connected.</Text><YStack backgroundColor="$backgroundHover" borderWidth={1} borderColor="$borderColor" borderRadius="$4" padding="$4" gap="$3"><XStack justifyContent="space-between" gap="$3" flexWrap="wrap"><H2 fontSize="$5">Today’s small steps</H2><Text id="progress-count" aria-live="polite">{done} of {rows.length} complete</Text></XStack><Progress value={rows.length?done/rows.length*100:0} size="$2" aria-label="Completed habits"><Progress.Indicator animation={undefined}/></Progress><Button size="$3" alignSelf="flex-start" onPress={()=>setFilter(!filter)}>{filter?'Show all habits':'Show unfinished only'}</Button><Separator/>{shown.length?shown.map(row=><XStack key={row.id} gap="$3" alignItems="center" paddingVertical="$2"><Checkbox id={'habit-'+row.id} checked={row.done} onCheckedChange={value=>setRows(rows.map(x=>x.id===row.id?{...x,done:value===true}:x))} aria-label={row.text} size="$4"><Checkbox.Indicator><Text fontWeight="700">✓</Text></Checkbox.Indicator></Checkbox><Text flex={1} fontSize="$4" lineHeight="$5" textDecorationLine={row.done?'line-through':'none'} opacity={row.done?.65:1}>{row.text}</Text></XStack>):<YStack gap="$3"><Text fontWeight="700">Nothing unfinished in this view</Text><Text>All your habits remain in the full list. You can revisit any completed item.</Text><Button onPress={()=>setFilter(false)}>Return to all habits</Button></YStack>}</YStack><YStack gap="$3"><Text htmlFor="habit-input" tag="label" fontWeight="700">Add one small habit</Text><Input id="habit-input" value={draft} onChangeText={v=>{setDraft(v);setError('')}} placeholder="For example, stretch for five minutes" aria-describedby={error?'habit-error':undefined} aria-invalid={!!error} onKeyPress={e=>{if(e.nativeEvent.key==='Enter')add()}}/><Text id="habit-error" color="$color" aria-live="polite">{error}</Text><XStack gap="$3" flexWrap="wrap"><Button theme="active" onPress={add}>Add habit</Button><Button onPress={()=>{setRows(initial);setDraft('');setError('');setFilter(false)}}>Reset sample board</Button></XStack></YStack><Text fontSize="$2" lineHeight="$3">Native Checkbox, Input, Button and Progress components; original board content and state. Theme changes preserve the current board.</Text></YStack></Theme></TamaguiProvider>}
createRoot(document.getElementById('root')).render(<App/>);
v5 프리셋을 사용해 Tamagui 2.7.7 웹 습관 보드를 완전하게 구현하세요. 크기, 간격, 모서리 반경, 글꼴 토큰 범주를 서로 구분하고, 생성된 테마 CSS, 밝고 어두운 파란색 하위 테마, 제어된 체크박스, 앞뒤 공백 제거 및 중복 검사, 전체 행을 기준으로 한 진행률 분모, 미완료 보기로 돌아가기, 모양을 바꾸지 않는 초기화를 유지하세요.
05
Tamagui 디자인 시스템의 출처와 범위
v5 프리셋을 사용하는 실제 Tamagui 2.7.7 웹 컴포넌트입니다. 습관 보드와 상태 규칙은 독자적으로 작성한 OpenDesign 코드이며, 계정·알림·네이티브 기기 동작은 연결되어 있지 않습니다.