# Workspace Web Design Language この文書は、Workspace Webのrendered resultが満たす設計規則を定義する。 一つの規則は最も具体的な一箇所だけをauthorityとする。具体的な二案の一方を棄却できない文は規則として残さない。 ## 1. Principles 複数の規則や要求が競合した場合だけ、この章の優先原理を使う。 ### Task before explanation 次の有効なactionへ到達する前の操作、scroll、常設説明が少ない案を選ぶ。ただし、説明を省くと対象、結果、permission、不可逆性を誤認する場合は説明をactionより前に置く。 ### Meaning before decoration 情報の関係、重要度、操作可能性、stateのいずれも変えない視覚差を追加しない。 ### Authority before convenience Backend authorityが確定していないdata、state、permission、actionを、UIの都合で補完または推測する案を棄却する。 ## 2. Semantics / Grammar ### Information structure - pageと、page内で一つのtaskまたは判断を完結させる領域をscopeとする。 - 各scopeで最も強い視覚要素はheading、現在判断が必要なattention、filled actionのいずれか一つだけにする。同じ強さのheading、attention、filled actionを複数置かない。 - hierarchyをcolorだけで表現しない。DOMと画面上の配置順を一致させた上で、size、weight、contrastを使う。 - scopeを識別するprimary titleは、一つのscopeにつき一度だけ表示する。 - actionは、そのactionが影響するscope内に置く。 - DTOにfieldが存在するという理由だけで表示せず、current taskの判断またはactionを変える値だけを常設する。 **Disclosure** - Raw dataやデバッグ情報など、通常は必要としない詳細な情報は折りたたみ領域に隠す。 - 作成または編集のformを表示用screenへ常時同居させず、明示的なactionで開く折りたたみ領域または別pageに置く。 ### Interaction and state - actionにはbutton、navigationにはlink、選択には対応するform controlを使う。 - interactive elementはhover、focus、active、disabledを視覚的に区別する。 - staticなstatus、key、metadataをbuttonやpillの形にしない。 - clickable rowは遷移先を一つだけ持ち、row全体で同じ遷移を実行する。 - permissionがないactionをdisabled controlとして表示しない。 **State and operation** - stateの違いによってユーザーの判断または利用できるactionが変わる場合、その違いを一つの表示へ統合しない。 - UI stateはそのstateを所有するauthorityから取得し、別domainやtransportのsignalから推測しない。 - 非同期mutationでは楽観的更新か悲観的更新を選ぶ。楽観的更新は暫定stateを明示し、failure時のrollbackまたは再取得を定義できる場合だけ使う。それ以外は確定済みstateを維持し、operationのpending stateを別に表示する。 - loading、failure、unavailableの表示は利用不能になった最小のscopeだけを置き換え、利用可能な親scopeとsibling scopeを残す。 - destructive actionは対象と結果を明記し、復元できない場合だけconfirmationを要求する。 ### Language / Copy - title、navigation label、control labelで明らかな内容を説明文で繰り返さない。 - 常設proseは、対象またはactionの選択を変えるwarning、permission、validation constraint、error recoveryに必要な内容だけにする。 - button labelには具体的なactionを表す動詞を使い、`Submit`と`OK`を使わない。 - identifier、API名、type名を別の語へ言い換えない。 ### Responsive behavior - page全体にhorizontal scrollを発生させない。 - wrappingまたはstack化によって情報の対応関係が失われる場合だけ、その領域内にbounded horizontal scrollを持たせる。それ以外のside-by-side layoutは一列にする。 - primary actionを初期viewportから追い出さない。 - contentをtruncateする場合、完全な値をcopyまたはdetailで取得できるようにする。 ### Accessibility - action、navigation、form controlを非semantic elementだけで実装しない。 - すべてのcontrolにaccessible nameを与える。 - keyboard focusを不可視にしない。 - tab orderを画面上の順序と一致させる。 - stateをcolorだけで区別しない。 - horizontal scroll regionをkeyboard focus可能にし、accessible nameを与える。 ## 3. Primitives / Tokens ### Color / Theme colorはCSS custom propertyをauthorityとし、通常のWorkspace colorはOKLCHで定義する。lightとdarkは同じsemantic token名を使う。 ```css --bg --bg-raised --bg-subtle --line --line-strong --text --text-strong --text-muted --text-faint --code --accent --accent-muted --success --warning --danger --interactive-hover --interactive-selected --shadow-overlay ``` - `--bg`、`--bg-raised`、`--bg-subtle`へhueを持たせない。 - accentはfocus、current navigation、current selectionだけに使い、通常本文や通常actionの背景へ使わない。 - success、warning、dangerを、それぞれ対応するstate以外へ使わない。 - componentへraw colorを追加せず、上記semantic tokenを使う。 - ConsoleとterminalのANSI paletteを通常のstatusやformへ流用しない。 ### Typography font sizeは、その領域でユーザーが行う読み方によって選ぶ。 - defaultは`14px / 20px`とし、個々のtextを読むこと自体がtaskの中心になる領域に使う。 - 同じ構造の反復を一覧として走査する領域と、位置、順序、形、選択状態を中心に識別するUI chromeには`12px / 16px`を使う。textを読まなければactionの意味や結果を判断できない場合は`14px / 20px`を使う。 - `24px / 32px`はページまたはdocument内で唯一かつ最大のheadingだけに使う。 - 収めるため、または情報を弱く見せるためにfont sizeを下げない。`12px`未満と`13px`を使わない。 - 機械的な値にはmonospace familyを使う。 ```css --font-sans --font-mono --font-size-title /* 24px */ --line-height-title /* 32px */ --font-size-body /* 14px */ --line-height-body /* 20px */ --font-size-compact /* 12px */ --line-height-compact /* 16px */ ``` ### Spacing / Shape ```css --space-1 /* 4px */ --space-2 /* 8px */ --space-3 /* 12px */ --space-4 /* 16px */ --space-5 /* 24px */ --space-6 /* 32px */ --radius-soft ``` - 一つの境界にspacing、line、surface backgroundを重ねず、primary cueを一つだけ使う。 - 汎用的な`Card` primitiveを設けない。 - nested surfaceでparentと同じ境界表現を繰り返さない。 - shadowは通常flowから浮くmenu、popover、Tooltipだけに使う。 - radiusはinteractive controlだけに使う。 ### Motion - motion完了をactionの受付、state change、content理解の条件にしない。 - 繰り返しまたは連続animationを、`prefers-reduced-motion: reduce`で停止する。 ## 4. Components / Patterns ### Border / Structural edge - `Bevel`は1pxの単色borderを描くvisual wrapperであり、照明、raised/inset、ridge/grooveを表現しない。 - `Bevel`自体へbackgroundやsurface toneを与えず、必要なbackgroundは内側のsemantic childが持つtext/content areaへ適用する。 - 隣接するsurfaceでは、実際に境界となる辺だけを有効にする。 - standaloneな閉じた領域は全辺を`Bevel`で囲う。Desktop shellではHeaderのbottom edgeとSidebarのright edgeだけを有効にし、viewport外周のtop/left edgeを描かない。 - `BevelLine`は開いた領域内の1px separatorだけに使う。同じlayerではheading、本文、Lineの端を揃え、Lineだけに端方向のpaddingやmarginを追加しない。 - nested layerでは親layoutがそのlayer全体へinline方向の余白を与え、content、row、Lineを一緒に移動する。Lineだけを短くしない。 - control自身のborder、focus ring、status marker、table gridを`Bevel`または`BevelLine`で置き換えない。 ### Sidebar - Desktop幅は`clamp(220px, 20vw, 280px)`とする。 - navigation contentだけをscrollさせる。 - Desktopのfold controlはSidebar下部、Mobileのfold controlはHeaderに置く。 - MobileでSidebarを表示するときはHeader下の残りviewport全体を使い、main contentと同時表示しない。 - navigationとscope control以外を置かない。 - navigation linkはSidebar幅全体を使うflat rowとする。 - active routeは一つの視覚cueと`aria-current="page"`で示す。 - iconだけでnavigation labelを置き換えない。 - fold controlは一つだけ置き、current stateに対応するaccessible nameを使う。 ### Tooltip / Contextual help - operationやtable全体を説明する常設proseを置く前に、button labelとcolumn headingだけで識別できるようにする。 - labelだけでは表現できない補助説明だけを、対象へbindしたTooltipに入れる。 - pointer hoverとkeyboard focusの両方で開く。 - triggerとTooltipを`aria-describedby`で関連付ける。 - hoverはdelay後、focusは直ちに表示する。 - `Escape`で閉じ、triggerからfocusを移動しない。 - 複数paragraph、form、link、buttonを入れない。 - error、permission、validation constraint、不可逆操作の結果、current stateをTooltipへ入れない。 - touch環境ではTooltipだけを説明への到達手段にしない。 ### Generic component patterns - **Form field**: label、control、constraintまたはhelp、field errorの順に配置する。 - **Key-value**: 一つのresourceの属性には`dl`を使う。 Design-labではcomponent名と実際のsampleだけを表示する。規則とrationaleをUIへ書かない。