# Kobalte 디자인 시스템

## Kobalte 디자인 시스템 인터페이스 미리보기

재료 양을 조정하고 재료를 모아 원본 레시피를 로컬에 고정하세요.

## Kobalte 디자인 시스템 디자인 참고 자료

Kobalte 0.13.14는 헤드리스 Solid 프리미티브를 제공하며, 표준 시각 규격은 제공하지 않습니다. 아래의 모든 recipe-* 색상과 치수는 애플리케이션의 전체 원본 설정입니다. API의 이름과 상태 속성은 설치된 패키지와 공식 컴포넌트 문서를 대조해 확인했습니다.

### 원본 시맨틱 색상

| 이름 / 규칙 | 값 / 구성 | 용도 |
| --- | --- | --- |
| --recipe-canvas | #f6f2ec | 바깥쪽 캔버스 |
| --recipe-surface | #fffefa | 레시피 및 분량 패널 |
| --recipe-ink | #403a30 | 기본 텍스트 |
| --recipe-muted | #807465 | 보조 텍스트 |
| --recipe-line | #e6ded2 | 구분선 및 테두리 |
| --recipe-action | #95612f | 선택된 체크박스 및 동작 텍스트 |
| --recipe-on-action | #fff | 채워진 동작의 전경색 |
| --recipe-focus | #b88955 | 표시되는 키보드 포커스 |
| --recipe-soft | #f1e4d3 | 스테퍼 및 눌린 핀 배경 |
### 원본 타이포그래피 및 치수

| 이름 / 규칙 | 값 / 구성 | 용도 |
| --- | --- | --- |
| Body | system-ui, sans-serif | 글꼴 다운로드 없음 |
| Page heading | 34px / 1.15; 29px narrow | 원본 계층 구조 |
| Recipe title | Georgia, serif; 30px / 1.2 | 원본 편집 스타일 강조색 |
| Ingredient copy | 13px / 1.5; 12px narrow | 읽기 쉽게 줄바꿈되는 레이블 |
| --recipe-space-1 / 2 / 3 | 4px / 8px / 12px | 좁은 간격 |
| --recipe-space-4 / 6 / 8 | 16px / 24px / 32px | 패널 및 페이지 여백 |
| Panel radius | 16px | 원본 표면 형태 |
| Checkbox control | 21 × 21px; 5px radius | 원본 표시 인디케이터 |
| Stepper trigger | 42px wide | 원본 형태를 적용한 기본 트리거 |
| Layout breakpoint | 760px | 두 열을 한 열로 전환 |
| Focus ring | 3px; 3px offset | 원본 키보드 포커스 표시 방식 |
### 기본 컴포넌트 상태 규약

| 이름 / 규칙 | 값 / 구성 | 용도 |
| --- | --- | --- |
| NumberField.rawValue | number | 서식이 적용된 텍스트가 아닌 산술 상태 |
| NumberField.onRawValueChange | (value: number) => void | 수량을 업데이트하고 오래된 선택을 해제 |
| minValue / maxValue / step | 1 / 8 / 1 | 애플리케이션 제한을 기본 숫자 필드에 전달 |
| formatOptions.maximumFractionDigits | 0 | 정수 분량 표시 |
| validationState | valid \| invalid | 애플리케이션의 유효성 상태를 기본 필드에 반영 |
| Checkbox.checked / onChange | boolean / callback | 제어되는 재료 선택 |
| Checkbox data attributes | data-checked; data-focus-visible | 기본 선택 및 키보드 포커스 상태 스타일 지정 |
| ToggleButton.pressed / onChange | boolean / callback | 제어되는 로컬 핀 |
| ToggleButton data-pressed | present when pressed | 로컬 핀의 채워진 상태 스타일 |
## Kobalte 디자인 시스템 전체 가이드

실제 인터페이스를 위해 이 시스템을 선택하고 적용하고 검토하는 실용 가이드.

완전한 안내와 구현 시작점, 소스 링크.

## Kobalte 디자인 시스템: Kobalte 컴포넌트를 Solid 시그널에 연결하기

### Kobalte 컴포넌트를 Solid 시그널에 연결하기

> 이 컴포넌트는 Solid 시그널로 분량, 모은 재료, 핀 상태를 관리합니다. createMemo는 숫자 시그널에서 유효성을 도출하고, For는 안정적인 재료 목록을 렌더링합니다. Kobalte의 제어형 props는 이 시그널을 직접 읽으므로, 별도의 폼 프로바이더나 React 방식의 상태 어댑터 없이도 업데이트할 수 있습니다.

NumberField, Checkbox, ToggleButton은 각 컴포넌트의 진입점에서 가져옵니다. 각 컴포넌트의 역할은 서로 다릅니다. 숫자 필드는 편집 가능한 숫자 입력을 처리하고, 각 체크박스는 모은 재료 하나를 나타내며, 토글은 레시피 핀 상태를 유지합니다. 산술 및 무효화 규칙은 시각 선택자에 넣지 않고 애플리케이션 함수에서 처리합니다.

지원 사양 읽기

## Kobalte 디자인 시스템: 숫자 상태와 서식이 적용된 텍스트를 분리하기

### 숫자 상태와 서식이 적용된 텍스트를 분리하기

NumberField에는 rawValue={portions()}와 onRawValueChange={update}를 전달합니다. 숫자 시그널은 곱셈을 직접 처리하고, 입력 필드는 서식 적용과 사용자 입력을 담당합니다. 서식이 적용된 표시 문자열을 산술 연산에 다시 사용하면 구분 기호나 로캘에 대한 파싱 가정이 생기며, 네이티브 프리미티브가 이미 처리하는 문제를 다시 다루게 됩니다.

이 예제에서는 정수 기준으로 1인분에서 8인분까지 허용하고, step은 1, maximumFractionDigits는 0으로 설정합니다. 편집 중인 필드에는 불완전하거나 잘못된 값이 잠시 들어갈 수 있으므로, 별도의 계산 조건식으로 유한성, 정수 여부, 허용 범위를 확인합니다. 값이 유효하지 않으면 합계에 대시(—)를 표시하고 재료 체크박스를 비활성화합니다. 사용자가 유효한 값을 입력한 것처럼 보이도록 그럴듯하지만 틀린 수량을 표시하지 않습니다.

지원 사양 읽기

## Kobalte 디자인 시스템: 네이티브 레이블과 메시지로 Field 구성하기

### 기본 레이블과 메시지로 필드 구성하기

숫자 필드는 Label, DecrementTrigger, Input, IncrementTrigger, Description, ErrorMessage로 구성됩니다. 표시되는 설명에는 허용 범위와 인분 수 변경에 따른 결과가 모두 안내됩니다. 트리거에는 ‘인분 줄이기’, ‘인분 늘리기’라는 의미 있는 레이블을 지정하므로, 기호인 빼기와 더하기 표시만으로 접근성 이름을 전달할 필요가 없습니다.

validationState는 애플리케이션의 조건식에 따라 valid와 invalid 사이에서 전환됩니다. ErrorMessage는 페이지의 다른 곳에 배치된 무관한 문단이 아니라 해당 네이티브 필드에 속합니다. 제출되는 폼의 경우 Kobalte는 원시값 직렬화를 위한 HiddenInput도 제공합니다. 이 로컬 예제에는 제출 엔드포인트나 폼 페이로드가 없으므로 포함하지 않습니다.

지원 사양 읽기

## Kobalte 디자인 시스템: 재료 선택 상태를 변경 불가능한 Set으로 관리하기

### 재료 선택 상태를 불변 Set으로 관리하기

각 재료에는 Checkbox의 전체 구성 요소인 Input, Control, Indicator, Label을 사용합니다. 실제 입력 요소는 의미와 키보드 상호작용을 유지하고, 화면에 보이는 컨트롤은 체크 표시와 기존 상태 스타일을 적용합니다. 레이블에는 재료 이름과 양을 모두 포함하여 사용자가 준비할 항목의 수량도 전달합니다.

mark는 이전 선택 항목을 바탕으로 새 Set을 만들고 ID를 추가하거나 제거합니다. 기존 Set만 변경하면 동일하게 안정적인 시그널 업데이트를 보장할 수 없습니다. 렌더링되는 개수는 Set의 크기를 읽고, 각 Checkbox는 안정적인 재료 ID를 기준으로 포함 여부를 확인합니다. 인분 수가 바뀌면 선택 항목 전체를 지웁니다. 320그램을 모았다는 사실만으로 새로 필요한 400그램도 충족했다고 볼 수 없기 때문입니다.

- 화면에 보이는 모든 재료 이름을 Checkbox.Label과 연결합니다.

- 화면에 보이는 사각형을 사용자 지정 스타일로 꾸미더라도 Checkbox.Input은 유지합니다.

- 선택 상태에는 안정적인 ID를 사용하고, 업데이트할 때마다 새 Set을 만듭니다.

지원 사양 읽기

## Kobalte 디자인 시스템: 유지되는 로컬 선택에는 Toggle 사용하기

### 지속적으로 유지되는 로컬 선택에는 Toggle 사용하기

고정 기능에는 pressed와 onChange를 사용하는 ToggleButton을 쓰며, 캡션만 바뀌는 일반 버튼은 사용하지 않습니다. pressed 상태는 네이티브 의미와 data-pressed 선택자에 반영됩니다. 레이블은 체크 표시와 함께 ‘고정됨’으로 바뀌고, 인접한 상태 안내에는 이 설정이 로컬 미리보기에만 적용된다고 설명합니다.

고정해도 재료의 양이나 선택 상태는 바뀌지 않습니다. 인분 수를 바꾸면 재료 체크는 지워지지만, 레시피 자체는 그대로이므로 고정 상태는 유지됩니다. 기본 레시피 복원은 세 가지 상태를 함께 초기화합니다. 즉, 인분은 4로 되돌리고 재료 Set은 비우며 고정 상태는 해제합니다. 상호작용마다 모든 상태를 지우는 대신 각 상태의 의미에 맞춰 초기화 범위를 다르게 설정합니다.

지원 사양 읽기

## Kobalte 디자인 시스템: 헤드리스 컨트롤에 재현 가능한 시각적 설정 적용하기

### 헤드리스 컨트롤에 재현 가능한 시각 설정 적용하기

레시피 스타일시트에서는 숫자 입력의 편집 영역, 스테퍼 트리거, 체크박스 선택 상태의 역할을 구분합니다. 재료 텍스트는 체크한 뒤에만 흐린 색으로 표시합니다. 수량은 읽을 수 있게 유지하여 사용자가 준비한 양과 현재 인분에 필요한 양을 비교할 수 있도록 합니다.

간격에는 4, 8, 12, 16, 24, 32픽셀을 사용합니다. 기존 타이포그래피 계층에서는 컨트롤에 시스템 산세리프를, 레시피 제목에 Georgia를 사용합니다. 21픽셀 크기의 시각적 체크박스 옆에 연결된 전체 레이블을 배치하며, 실제로 활성화하기 편리한 영역은 레이블입니다. 이 값들은 전체 CSS에 정확히 기재되어 있으므로, 스크린샷을 보고 추측하지 않아도 예제를 재현할 수 있습니다.

지원 사양 읽기

## Kobalte 디자인 시스템: 정보를 숨기지 않고 레이아웃 축소하기

### 정보를 숨기지 않고 레이아웃 축소하기

넓은 화면에서는 유연한 레시피 카드와 너비 280픽셀의 인분 패널을 나란히 배치합니다. 너비가 760픽셀일 때는 한 열로 쌓습니다. 카드 제목은 줄바꿈될 수 있고, 재료 레이블은 다음 줄로 이어질 수 있으며, 양은 줄바꿈되지 않는 간결한 값으로 표시됩니다. 가장 작은 브레이크포인트에서는 양을 숨기는 대신 패널 패딩과 레이블 글자 크기를 약간 줄입니다.

샐러드 일러스트는 독창적인 CSS이며 장식용으로 표시됩니다. 레시피 이름, 인분 수, 재료별 양은 일러스트 없이도 정보를 전달합니다. 일러스트를 바꾸거나 카드 너비를 줄일 때도 전체 재료 레이블과 양을 유지하세요. 이 값들이 재료를 준비하는 작업의 실제 입력입니다.

지원 사양 읽기

## Kobalte 디자인 시스템: 수량 변경에 따른 종속 상태 업데이트 처리하기

### 수량 변경을 종속 상태 업데이트로 처리하기

인분 1개당 다섯 가지 재료의 양을 합하면 250그램입니다. 인분이 4에서 5로 늘어나면 표시되는 총량은 1,000그램에서 1,250그램으로, 오렌지 필요량은 320그램에서 400그램으로 바뀝니다. 이전의 체크 상태는 기존 수량을 기준으로 했으므로 지우지만, 레시피 자체는 그대로이므로 고정 상태는 유지합니다.

업데이트 함수는 먼저 Object.is로 들어온 값과 현재 시그널 값을 비교합니다. 값이 바뀌지 않은 콜백은 체크리스트를 지우거나 수량 변경을 알리지 않아야 합니다. 유효하지 않은 값은 수량 표시를 억제하고 재료 준비 기능을 비활성화하며, 오류 메시지에 허용 범위를 안내합니다. 이렇게 하면 입력 중간 상태는 감지하면서도 잘못된 숫자로 오해를 부르는 합계가 표시되는 것을 방지할 수 있습니다.

1. 인분을 4로 설정한 상태에서 오렌지 조각을 선택합니다.

2. 인분을 5로 늘린 다음 오렌지 400g, 총량 1,250g, 준비한 항목 0개가 표시되는지 확인합니다.

3. 레시피를 고정한 다음 기본값을 복원하고 모든 로컬 상태가 초기화되는지 확인하세요.

지원 사양 읽기

## Kobalte 디자인 시스템: Solid 빌드 사용 및 전체 스타일시트 포함

### Solid 빌드를 사용하고 전체 스타일시트 포함하기

이 설정은 Vite Solid 템플릿을 사용하고 검증된 Solid 및 Kobalte 버전을 고정하며 명시적으로 지정된 앱 루트에 마운트합니다. 위에 표시된 컴포넌트와 스타일시트는 모두 완성된 파일입니다. 이 예제에는 테마 공급자, 아이콘 패키지, 외부 글꼴 또는 백엔드가 필요하지 않습니다.

다른 애플리케이션에 통합할 때는 프레임워크 컴파일러와 컴포넌트 생성을 서로 맞추세요. React 변환을 사용해도 Solid JSX가 올바른 Kobalte 애플리케이션으로 변환되지는 않습니다. 마찬가지로 Kobalte API의 향후 메이저 버전으로 고정된 종속성을 교체하려면 해당 버전의 문서를 확인해야 합니다. 레시피 수량과 팔레트는 샘플 애플리케이션 데이터이므로, 이를 조정해도 네이티브 프리미티브 계약을 변경할 필요는 없습니다.

지원 사양 읽기

## Kobalte 디자인 시스템 컴포넌트 예제

### 반응형 레시피 컴포넌트 전체 코드

명시적인 잘못된 입력 및 초기화 동작을 갖춘 네이티브 숫자 필드, 체크박스 구성 요소 및 토글 버튼입니다.

```
import {createSignal,createMemo,For} from 'solid-js';
import {NumberField} from '@kobalte/core/number-field';
import {Checkbox} from '@kobalte/core/checkbox';
import {ToggleButton} from '@kobalte/core/toggle-button';
export default function App(){
 const[portions,setPortions]=createSignal(4),[checked,setChecked]=createSignal(new Set()),[pinned,setPinned]=createSignal(false),[notice,setNotice]=createSignal('');
 const ingredients=[{id:'orange',name:'Orange segments',grams:80},{id:'beans',name:'Cooked white beans',grams:90},{id:'leaves',name:'Salad leaves',grams:25},{id:'fennel',name:'Thinly sliced fennel',grams:40},{id:'dressing',name:'Citrus dressing',grams:15}];
 const valid=createMemo(()=>Number.isFinite(portions())&&Number.isInteger(portions())&&portions()>=1&&portions()<=8);
 function update(value){if(Object.is(value,portions()))return;setPortions(value);setChecked(new Set());setNotice('Portions changed. Ingredient checks were cleared for the new quantities.')}
 function mark(id,value){setChecked(previous=>{const next=new Set(previous);value?next.add(id):next.delete(id);return next})}
 function reset(){setPortions(4);setChecked(new Set());setPinned(false);setNotice('Restored four portions and an unchecked ingredient list.')}
 return <main class="recipe-shell"><header><p class="eyebrow">KITCHEN NOTEBOOK · REACTIVE RECIPE CARD</p><h1>A brighter bowl, sized for your table.</h1><p>Change the portions, check the scaled ingredients and pin the recipe locally. The quantities are original sample data for this interface.</p></header><div class="recipe-layout"><section class="recipe-card"><div class="salad-art" aria-hidden="true"><div class="plate"><i/><b/><em/><span/><strong/></div></div><div class="recipe-heading"><div><p class="eyebrow">CITRUS & WHITE BEAN SALAD</p><h2>Fresh, crisp, generous.</h2></div><ToggleButton class="pin" pressed={pinned()} onChange={value=>{setPinned(value);setNotice(value?'Recipe pinned in this local preview.':'Recipe unpinned in this local preview.')}} aria-label="Pin recipe">{pinned()?'Pinned ✓':'Pin recipe'}</ToggleButton></div><p class="recipe-description">Sweet citrus, tender beans and crisp fennel. Use the checklist to gather the amounts for your chosen portion count.</p><div class="ingredient-title"><h3>Ingredients</h3><span>{checked().size} / 5 gathered</span></div><div class="ingredients"><For each={ingredients}>{ingredient=><Checkbox class="ingredient" checked={checked().has(ingredient.id)} onChange={value=>mark(ingredient.id,value)} disabled={!valid()}><Checkbox.Input/><Checkbox.Control class="check-control"><Checkbox.Indicator>✓</Checkbox.Indicator></Checkbox.Control><Checkbox.Label class="ingredient-label"><span>{ingredient.name}</span><strong>{valid()?ingredient.grams*portions()+' g':'—'}</strong></Checkbox.Label></Checkbox>}</For></div></section><aside class="portion-panel"><h2>Make it yours</h2><NumberField rawValue={portions()} onRawValueChange={update} minValue={1} maxValue={8} step={1} formatOptions={{maximumFractionDigits:0}} validationState={valid()?'valid':'invalid'} class="portion-field"><NumberField.Label class="field-label">Portions</NumberField.Label><div class="stepper"><NumberField.DecrementTrigger aria-label="Fewer portions">−</NumberField.DecrementTrigger><NumberField.Input/><NumberField.IncrementTrigger aria-label="More portions">+</NumberField.IncrementTrigger></div><NumberField.Description class="field-description">Choose 1–8 whole portions. Changing the quantity clears ingredient checks.</NumberField.Description><NumberField.ErrorMessage class="field-error">Enter a whole number from 1 to 8.</NumberField.ErrorMessage></NumberField><div class="portion-total"><span>Total ingredients</span><strong>{valid()?ingredients.reduce((s,x)=>s+x.grams,0)*portions()+' g':'—'}</strong></div><h3>A simple preparation order</h3><ol><li>Gather the scaled amounts and drain the cooked beans.</li><li>Combine the orange, fennel and leaves in a bowl.</li><li>Add the beans and dressing, then toss gently.</li></ol><button class="reset" onClick={reset}>Restore default recipe</button><p role="status" class="notice">{notice()||(pinned()?'Recipe pinned in this local preview.':'No shopping order or account update is created.')}</p></aside></div></main>
}
```

코드 참조

- React JSX 컴파일러가 아닌 Solid Vite 템플릿을 사용하세요.

- rawValue에는 숫자 상태가 담기며, 모든 재료 수량은 이 값을 기준으로 계산됩니다.

- 분량을 변경하면 이전 수량이 더 이상 최신 값이 아니므로 체크된 Set이 비워집니다.

### 원본 시각 설정 전체

시맨틱 사용자 지정 속성, 타이포그래피 계층, 간격, 상태 스타일 및 독창적인 CSS 일러스트레이션입니다.

```
:root{--recipe-canvas:#f6f2ec;--recipe-surface:#fffefa;--recipe-ink:#403a30;--recipe-muted:#807465;--recipe-line:#e6ded2;--recipe-action:#95612f;--recipe-on-action:#fff;--recipe-focus:#b88955;--recipe-soft:#f1e4d3;--recipe-space-1:4px;--recipe-space-2:8px;--recipe-space-3:12px;--recipe-space-4:16px;--recipe-space-6:24px;--recipe-space-8:32px}html,body,#app{height:auto;min-height:0;margin:0}*{box-sizing:border-box}body{font-family:system-ui,sans-serif;color:var(--recipe-ink);background:var(--recipe-canvas)}button,input{font:inherit}button{cursor:pointer}button:disabled{opacity:.4;cursor:default}button:focus-visible,input:focus-visible{outline:3px solid var(--recipe-focus);outline-offset:3px}.recipe-shell{max-width:1090px;margin:auto;padding:32px}.eyebrow{font-size:10px;font-weight:700;letter-spacing:.13em;color:var(--recipe-muted)}h1{font-size:34px;line-height:1.15;max-width:700px;margin:12px 0}header>p:last-child{font-size:14px;line-height:1.7;color:var(--recipe-muted);max-width:630px}.recipe-layout{display:grid;grid-template-columns:minmax(0,1fr) 280px;gap:24px;margin-top:28px}.recipe-card,.portion-panel{background:var(--recipe-surface);border:1px solid var(--recipe-line);border-radius:16px;padding:24px;min-width:0}.portion-panel{align-self:start}.salad-art{height:180px;background:#e8e4d5;border-radius:10px;position:relative;overflow:hidden;margin-bottom:24px}.plate{position:absolute;width:210px;height:210px;left:50%;top:18px;transform:translateX(-50%);border:18px solid #faf8f0;border-radius:50%;background:#dedcbf;box-shadow:0 10px 25px #5e584b1c}.plate i,.plate b,.plate em,.plate span,.plate strong{position:absolute;display:block}.plate i{width:85px;height:42px;border-radius:0 0 85px 85px;background:#dc9658;border:6px solid #f7c888;left:14px;top:18px;transform:rotate(-23deg)}.plate b{width:72px;height:36px;border-radius:0 0 72px 72px;background:#dc9658;border:6px solid #f7c888;left:75px;top:83px;transform:rotate(28deg)}.plate em{width:50px;height:77px;background:#83976a;border-radius:80% 0 80% 0;left:108px;top:4px;transform:rotate(20deg)}.plate span{width:30px;height:50px;background:#667f56;border-radius:80% 0 80% 0;left:25px;top:88px;transform:rotate(-23deg)}.plate strong{width:24px;height:34px;background:#fff3d7;border-radius:50%;left:80px;top:46px;box-shadow:-34px 14px 0 #fff3d7,25px 28px 0 #fff3d7}.recipe-heading{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap}.recipe-heading h2{font:400 30px/1.2 Georgia,serif;margin:8px 0}.pin{border:1px solid var(--recipe-line);background:white;border-radius:999px;padding:9px 13px;font-size:11px;color:var(--recipe-action)}.pin[data-pressed]{background:var(--recipe-soft);border-color:var(--recipe-action)}.recipe-description{font-size:13px;line-height:1.7;color:var(--recipe-muted)}.ingredient-title{display:flex;justify-content:space-between;align-items:center;gap:12px;margin-top:24px}.ingredient-title h3{font-size:16px;margin:0}.ingredient-title span{font-size:11px;color:var(--recipe-muted)}.ingredients{margin-top:14px}.ingredient{display:flex;gap:12px;align-items:center;border-top:1px solid var(--recipe-line);padding:16px 0;cursor:pointer}.check-control{height:21px;width:21px;border:1px solid #b9ab98;border-radius:5px;flex-shrink:0;display:flex;justify-content:center;align-items:center;font-size:13px}.check-control[data-checked]{background:var(--recipe-action);color:white;border-color:var(--recipe-action)}.check-control[data-focus-visible]{outline:3px solid var(--recipe-focus);outline-offset:3px}.ingredient-label{display:flex;justify-content:space-between;gap:12px;width:100%;font-size:13px;line-height:1.5;cursor:pointer}.ingredient-label strong{white-space:nowrap;font-size:12px}.ingredient[data-checked] .ingredient-label>span{text-decoration:line-through;color:var(--recipe-muted)}.portion-panel>h2{font-size:20px;margin:0 0 24px}.field-label{font-size:13px;font-weight:600}.stepper{display:flex;margin-top:10px;border:1px solid var(--recipe-line);border-radius:8px;overflow:hidden}.stepper button{width:42px;flex-shrink:0;background:var(--recipe-soft);border:0;font-size:21px}.stepper input{width:100%;min-width:0;text-align:center;border:0;padding:12px 0;background:white;color:var(--recipe-ink)}.field-description{font-size:11px;line-height:1.7;color:var(--recipe-muted);margin:10px 0 0}.field-error{font-size:12px;color:#a73324}.portion-total{display:flex;justify-content:space-between;gap:12px;margin:24px 0;padding:18px 0;border-top:1px solid var(--recipe-line);border-bottom:1px solid var(--recipe-line);font-size:12px}.portion-total strong{font-size:19px;white-space:nowrap}.portion-panel h3{font-size:13px;line-height:1.6}.portion-panel ol{padding-left:18px;font-size:12px;line-height:1.7;color:var(--recipe-muted)}.portion-panel li{margin-bottom:12px}.reset{width:100%;padding:12px;border:1px solid var(--recipe-line);border-radius:8px;background:white;color:var(--recipe-action);font-size:12px;margin-top:16px}.notice{font-size:11px;line-height:1.7;color:var(--recipe-muted)}@media(max-width:760px){.recipe-layout{grid-template-columns:minmax(0,1fr)}.recipe-shell{padding:20px}h1{font-size:29px}}@media(max-width:360px){.recipe-shell{padding:14px}.recipe-card,.portion-panel{padding:16px}.ingredient-label{gap:8px;font-size:12px}.ingredient{gap:8px}.ingredient-label strong{font-size:11px}.recipe-heading h2{font-size:27px}}
```

코드 참조

- recipe-* 값은 애플리케이션에서 직접 선택한 값이며, 공식 Kobalte 토큰이 아닙니다.

- 전체 스타일시트에는 반응형 레이아웃과 뚜렷하게 표시되는 포커스 스타일이 포함됩니다.

## Kobalte 디자인 시스템 에이전트 설정

### 구현 시작점

Solid Vite 템플릿에는 필요한 Solid JSX 컴파일러가 포함되어 있습니다. 위의 App.jsx 및 style.css 예제에는 애플리케이션 코드와 스타일이 모두 들어 있으므로 공급자나 외부 서비스가 필요하지 않습니다.

```
// npm create vite@latest kobalte-recipe -- --template solid
// cd kobalte-recipe && npm install
// npm install solid-js@1.9.15 @kobalte/core@0.13.14
// Save the complete examples as src/App.jsx and src/style.css.
// src/index.jsx:
import {render} from 'solid-js/web';
import App from './App.jsx';
import './style.css';
render(() => <App />, document.getElementById('app'));
// index.html: <div id="app"></div><script type="module" src="/src/index.jsx"></script>
// npm run dev
```

### 프로젝트를 위한 프롬프트

```
반응형 레시피 카드를 재현하려면 Solid 1.9.15와 함께 Kobalte 0.13.14를 사용하세요. NumberField의 rawValue/onRawValueChange, 1부터 8까지의 범위, 정수 분량 유효성 검사, Label/Description/ErrorMessage 구성 요소를 모두 유지하세요. 분량 시그널을 기준으로 재료 중량과 총량을 계산하고, 실제 수량 변경 시에는 변경 불가능한 체크된 ID Set을 비우되 레시피 고정은 유지하세요. 네이티브 Checkbox 구성 요소와 ToggleButton의 pressed/onChange를 사용하고, 수량이 유효하지 않을 때는 담기를 비활성화하세요. 초기화하면 분량을 4로 되돌리고 체크와 고정을 해제하세요. 독창적인 전체 레시피 CSS를 포함하고 모든 상태를 로컬에 유지하세요.
```

## Kobalte 디자인 시스템의 출처 및 범위

Kobalte 0.13.14 및 Solid 1.9.15에서 검증했습니다. recipe-* 토큰, CSS 일러스트레이션 및 재료 분량은 모두 독창적인 예제 데이터입니다. 고정과 담기는 로컬 상태로만 처리되며, 계정, 쇼핑 서비스 또는 영구 저장되는 레시피 저장소에 연결되지 않습니다.

- 시작하기 Solid 설정 및 컴포넌트 가져오기입니다.

- 스타일링 헤드리스 스타일링 및 상태 선택자입니다.

- 숫자 필드 숫자 값, 범위, 필드 구성 및 가공되지 않은 폼 입력입니다.

- 체크박스 Input, Control, Indicator 및 Label입니다.

- 토글 버튼 눌림 상태, 콜백 및 키보드 동작입니다.

## Kobalte 디자인 시스템 관련 자료

### Mantine

기본 참가자 표와 일정 관리 드로어를 중심으로 구성한 Mantine 9 참조입니다. 실제 테마 스케일을 살펴보고, useForm을 TextInput, Select, Checkbox, DatePickerInput에 연결한 뒤 로컬 레코드를 추가하기 전에 유효성 검사를 시험해 보세요.

### Melt UI

Melt UI의 스토어/액션 빌더는 사용자가 소유한 Svelte 마크업에 접근성 동작을 연결합니다. 이 가이드에서는 @melt-ui/svelte 0.86.6을 사용해 속성, 액션, 쓰기 가능한 스토어를 통해 라디오, 슬라이더, 스위치 빌더를 연결합니다. 이는 최신 runes API와는 별개입니다.

### Naive UI

테마 객체의 값을 검증할 수 있는 네이티브 Naive UI 데이터 테이블 작업 화면입니다. 행 선택, 펼치기, 정렬, 페이지 이동 및 로컬 일괄 검토 기능을 제공합니다.


- [구현 레퍼런스](https://kobalte.dev/docs/core/components/number-field)
- [출처](https://kobalte.dev/docs/core/overview/styling)
- [출처](https://kobalte.dev/docs/core/components/number-field)
- [지원 사양 읽기](https://kobalte.dev/docs/core/overview/getting-started)
- [지원 사양 읽기](https://kobalte.dev/docs/core/components/number-field)
- [지원 사양 읽기](https://kobalte.dev/docs/core/components/checkbox)
- [지원 사양 읽기](https://kobalte.dev/docs/core/components/toggle-button)
- [지원 사양 읽기](https://kobalte.dev/docs/core/overview/styling)
- [코드 참조](https://kobalte.dev/docs/core/components/number-field)
- [코드 참조](https://kobalte.dev/docs/core/overview/styling)
- [시작하기 Solid 설정 및 컴포넌트 가져오기입니다.](https://kobalte.dev/docs/core/overview/getting-started)
- [스타일링 헤드리스 스타일링 및 상태 선택자입니다.](https://kobalte.dev/docs/core/overview/styling)
- [숫자 필드 숫자 값, 범위, 필드 구성 및 가공되지 않은 폼 입력입니다.](https://kobalte.dev/docs/core/components/number-field)
- [체크박스 Input, Control, Indicator 및 Label입니다.](https://kobalte.dev/docs/core/components/checkbox)
- [토글 버튼 눌림 상태, 콜백 및 키보드 동작입니다.](https://kobalte.dev/docs/core/components/toggle-button)
