ヘッドレスコンポーネントシステム · 新しいリソース

Kobalteデザインシステム.

Kobalteは Solid 向けのヘッドレスコンポーネントを提供します。生値と書式設定済みの値を分けて扱う数値フィールド、構成要素が明確なチェックボックス、押下状態を持つボタンなどがあります。このガイドでは、派生量とリセット可能な材料チェックを備えたレシピの分量調整ツールを通じて、これらのプリミティブと Solid のシグナルをつなぎます。

OpenDesign ガイド9 章リファレンス項目29件オリジナルアプリケーションガイド
01

Kobalteデザインシステムのインターフェースプレビュー

材料の分量を調整し、買い物リストに追加して、オリジナルのレシピをローカルにピン留めします。

リアクティブなレシピカード完全な例を開く
Kobalte 0.13.14とSolid 1.9.15を使用。ネイティブのヘッドレスプリミティブと、完全なオリジナルのCSS。 · 実装リファレンス
02

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 ステッパーと押下時のピン留め背景

オリジナルのタイポグラフィと寸法

ソース
名前 / ルール値 / 設定用途
Bodysystem-ui, sans-serifフォントのダウンロードなし
Page heading34px / 1.15; 29px narrow元の階層
Recipe titleGeorgia, serif; 30px / 1.2オリジナルのエディトリアルアクセント
Ingredient copy13px / 1.5; 12px narrow折り返しても読みやすいラベル
--recipe-space-1 / 2 / 34px / 8px / 12px小さな間隔
--recipe-space-4 / 6 / 816px / 24px / 32pxパネルとページの間隔
Panel radius16pxオリジナルのサーフェス形状
Checkbox control21 × 21px; 5px radiusオリジナルの視認可能なインジケーター
Stepper trigger42px wideオリジナルの形状を適用したネイティブトリガー
Layout breakpoint760px2列から1列に切り替え
Focus ring3px; 3px offsetオリジナルのキーボード操作時の表示

ネイティブコンポーネントの状態仕様

ソース
名前 / ルール値 / 設定用途
NumberField.rawValuenumber書式設定済みテキストではなく算術用の状態
NumberField.onRawValueChange(value: number) => void数量を更新し、古いチェックを解除する
minValue / maxValue / step1 / 8 / 1アプリケーションの制限値をネイティブ数値フィールドに渡す
formatOptions.maximumFractionDigits0整数の分量表示
validationStatevalid | invalidアプリケーションの有効性判定をネイティブフィールドに反映する
Checkbox.checked / onChangeboolean / callback材料の選択を制御する
Checkbox data attributesdata-checked; data-focus-visibleネイティブのチェック済み状態とキーボードフォーカス状態をスタイル設定する
ToggleButton.pressed / onChangeboolean / callbackローカルのピン留めを制御する
ToggleButton data-pressedpresent when pressedローカルのピン留めに塗りつぶしスタイルを適用する
03

Kobalteデザインシステム完全ガイド

実際のインターフェースで、このデザインシステムを選び、適用し、レビューするための実践ガイドです。

DESIGN-kobalte.md

詳しいガイダンス、実装の出発点、ソースへのリンクをまとめています。

ガイドをダウンロード

Kobalteデザインシステム:KobalteのコンポーネントをSolid Signalsに接続する

KobalteのコンポーネントをSolid Signalsに接続する

このコンポーネントでは、分量、買い物リストに追加した材料、ピン留めの状態にSolidのシグナルを使用します。createMemo は数値シグナルから有効性を導き出し、For は安定した材料コレクションを描画します。Kobalteの制御用propsはこれらのシグナルを直接読み取るため、更新に別のフォームプロバイダーやReact形式の状態アダプターは必要ありません。

この OpenDesign ガイドから

NumberField、Checkbox、ToggleButtonは、それぞれのコンポーネントのエントリーポイントからインポートします。それぞれの役割は異なります。NumberFieldは編集可能な数値入力を扱い、各Checkboxは買い物リストに追加した材料を1つ表し、ToggleButtonはレシピのピン留め状態を保持します。算術処理と無効化のルールは、ビジュアルセレクターに埋め込まず、アプリケーションの関数で管理します。

関連する仕様を確認する

Kobalteデザインシステム:数値の状態を表示用テキストと分けて管理する

数値の状態を表示用テキストと分けて管理する

NumberFieldには rawValue={portions()} と onRawValueChange={update} を渡します。数値シグナルはそのまま乗算に使用され、入力欄は値の書式設定とユーザーによる編集を処理します。書式設定済みの表示テキストを算術処理に戻すと、区切り文字やロケールに関する解析上の前提が必要になりますが、ネイティブプリミティブはすでにその問題に対応しています。

この例では、分量を1~8人分の整数とし、ステップを1、maximumFractionDigitsを0に設定しています。編集可能なフィールドには未入力や無効な値が一時的に入る可能性があるため、有限性、整数であること、範囲内であることを別の判定関数でも確認します。無効な間は合計にダッシュを表示し、材料のチェックボックスを無効にします。ユーザーが有効な値を入力していないのに、もっともらしい誤った数量を表示することはありません。

関連する仕様を確認する

Kobalteデザインシステム:ネイティブのラベルとメッセージでフィールドを構成する

ネイティブのラベルとメッセージでフィールドを構成する

この数値フィールドは、Label、DecrementTrigger、Input、IncrementTrigger、Description、ErrorMessageで構成されます。表示される説明には、範囲と分量変更の影響の両方を記載します。トリガーには「分量を減らす」「分量を増やす」という意味のあるラベルを付けるため、マイナス記号とプラス記号だけにアクセシブルネームを担わせる必要はありません。

validationState は、アプリケーションの判定関数に応じて valid と invalid を切り替えます。ErrorMessage は、そのネイティブフィールドに属するものであり、ページ内の別の場所に無関係な段落として配置するものではありません。送信するフォームでは、Kobalte が生の値をシリアライズするための HiddenInput も提供します。このローカルの例には送信先もフォームのペイロードもないため、HiddenInput は含まれていません。

関連する仕様を確認する

Kobalteデザインシステム:材料の選択を不変のSetで管理する

材料の選択を不変のSetで管理する

各材料では、Checkboxの構成要素であるInput、Control、Indicator、Labelをすべて使用します。実際のinputがセマンティクスとキーボード操作を保ち、表示用のコントロールがチェックマークとオリジナルの状態スタイルを担います。ラベルには材料名と分量の両方を含め、集める材料の一部である数量も読者に伝えます。

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、ピン留め解除の3つの状態がまとめてリセットされます。このように、すべての操作で一律に状態を消去するのではなく、それぞれの状態の意味に応じてリセットの範囲を定めています。

関連する仕様を確認する

Kobalte デザインシステム:ヘッドレスコントロールに再現可能なビジュアル設定を与える

ヘッドレスコントロールに再現可能なビジュアル設定を適用する

レシピのスタイルシートでは、数値入力の編集領域、ステッパーのトリガー、チェックボックスの選択機能を分けています。材料名の文字色を弱めるのはチェック後のみです。数量は読みやすいまま保たれるため、集めた量と現在の分量を引き続き比較できます。

スペーシングには4、8、12、16、24、32ピクセルを使用します。元のタイポグラフィでは、コントロールにシステムサンセリフ体、レシピタイトルに Georgia を組み合わせています。見た目が21ピクセルのチェックボックスは、関連付けられたラベル全体の横に配置します。操作しやすい領域として機能するのはラベルです。スクリーンショットから推測せずにこの見本を再現できるよう、これらの値をすべて CSS に記載しています。

関連する仕様を確認する

Kobalte デザインシステム:情報を隠さずにレイアウトを縮小する

情報を隠さずにレイアウトを縮小する

幅の広いレイアウトでは、可変幅のレシピカードと幅280ピクセルの分量パネルを並べます。幅760ピクセルでは、1列に積み重ねたレイアウトに切り替わります。カードの見出しは折り返し、材料名は複数行にできます。分量は折り返さずコンパクトに表示します。最小のブレークポイントでは、分量を隠すのではなく、パネルの余白とラベルの文字サイズを少し小さくします。

サラダのイラストはオリジナルの CSS で、装飾要素として扱います。レシピ名、分量、材料の数量は、イラストがなくても意味を伝えられます。イラストの変更やカード幅の縮小に際しても、材料名と数量をすべて保持してください。これらが材料を集めるタスクに必要な実際の情報です。

関連する仕様を確認する

Kobalte デザインシステム:数量の変更を依存する状態の更新として扱う

数量の変更を依存する状態の更新として扱う

1人分あたり、5種類の材料の合計は250グラムです。4人分から5人分にすると、合計表示は1,000グラムから1,250グラムに、オレンジの必要量は320グラムから400グラムに変わります。以前のチェックは変更前の数量に基づくため解除されます。一方、レシピ自体は同じなので、ピン留め状態は変わりません。

更新関数はまず、Object.is を使って入力された値を現在のシグナルと比較します。値に変更がないコールバックでは、チェックリストを消去したり、数量の変更を通知したりしません。無効な値の場合は数量を表示せず、材料を集める操作も無効にします。そのうえで、エラーメッセージに有効な範囲を示します。これらのルールにより、入力途中の変更を把握できる一方、無効な数値によって誤解を招く合計が表示されることはありません。

  1. 4人分のオレンジの房を選択します。
  2. 5人分に増やし、オレンジが400 g、合計が1,250 g、集めた材料が0件であることを確認します。
  3. レシピをピン留めしてからデフォルトに戻し、すべてのローカル状態がリセットされることを確認します。

関連する仕様を確認する

Kobalte デザインシステム:完全なスタイルシートを含むビルドを使用する

Solidビルドを使い、スタイルシート全体を含める

セットアップには Vite の Solid テンプレートを使い、検証済みの Solid と Kobalte のバージョンを固定して、明示的に指定した app root にマウントします。前述のコンポーネントとスタイルシートは、どちらも完全なファイルです。この見本にテーマプロバイダー、アイコンパッケージ、外部フォント、バックエンドは必要ありません。

別のアプリケーションに組み込む場合は、フレームワークのコンパイラーとコンポーネント生成方式を一致させてください。React のトランスフォームでは、Solid JSX を正しい Kobalte アプリケーションに変換できません。同様に、今後のメジャー版 Kobalte API に置き換える前に、そのバージョン固有のドキュメントを確認してください。レシピの数量と配色はサンプルアプリケーションのデータなので、変更してもネイティブプリミティブの仕様を変える必要はありません。

関連する仕様を確認する

Kobalte デザインシステムのコンポーネント例

リアクティブなレシピコンポーネントの全体

ネイティブの数値フィールド、チェックボックスの構造、無効時とリセット時の動作を明示したトグルボタン。

jsx
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 イラスト。

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 トークンではありません。
  • 完全なスタイルシートには、レスポンシブなレイアウトと視認できるフォーカス表示が含まれています。
04

Kobalte デザインシステムのエージェント設定

実装の出発点

Solid の Vite テンプレートには、必要な Solid JSX コンパイラーが含まれています。上記の App.jsx と style.css の例には、アプリケーションのコードとスタイルがすべて含まれているため、プロバイダーや外部サービスは必要ありません。

JavaScript
// npm create vite@latest kobalte-recipe -- --template solid
// cd kobalte-recipe && npm install
// npm install [email protected] @kobalte/[email protected]
// 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

あなたのプロジェクト向けプロンプト

AI エージェント用プロンプト
Kobalte 0.13.14 と Solid 1.9.15 を使って、リアクティブなレシピカードを再現します。NumberField の rawValue/onRawValueChange、最小値1・最大値8、整数の分量を検証する処理、Label/Description/ErrorMessage の構造をすべて維持してください。分量のシグナルから材料のグラム数と合計を算出します。実際に数量が変わった場合は、不変のチェック済みID Set をクリアしますが、レシピのピン留めは維持します。ネイティブの Checkbox パーツと pressed/onChange を備えた ToggleButton を使い、数量が無効な間は材料を集める操作を無効にします。リセット時は4人分、チェックなし、ピン留めなしに戻します。オリジナルのレシピ CSS をすべて含めます。状態はすべてローカルに保ちます。
05

Kobalte デザインシステムの出典と適用範囲

Kobalte 0.13.14 と Solid 1.9.15 で検証済みです。すべての recipe-* トークン、CSS のイラスト、材料の分量は、この見本用に作成したデータです。ピン留めと材料の収集はローカルの状態で管理し、アカウント、買い物サービス、永続的なレシピストアには接続しません。