Conventions
トークンや
Motion 9 rules
- transition の
duration は 0.15s か 0.2s の 二値。 easing は 書かない (既定の ease) 色・
背景色の 変化は 0.2s、 アイコンや 戻る ボタンなど 小さな chrome は 0.15s。 0.18s や 0.1s は 例外扱い。 color 0.2sbackground-color 0.2stransform 0.15s ease
- blur-up の
解除は 例外なく filter 250ms ease-in-out 5 アプリと
共有パッケージで 同一の 値。 プレースホルダは 幅 20px、 ぼかしは blur(36px)。 transition: filter 250ms ease-in-outfilter: blur(36px)width: 20
- :active は
押し込みの スケール。 要素が 大きい ほど 縮小率は 小さい カード 0.99、
タイルや チップ 0.98、 ボタン 0.95、 アイコンボタン 0.9。 scale(0.99)scale(0.98)scale(0.95)scale(0.9)
- hover は
彩度ではなく 明度で 示す。 --c-sub から --c-text へ opacity を
下げる hover は 使わない。 既定色が --c-text の ブランドリンク(共有ヘッダーの タイトル)は 逆向きに --c-sub へ、 画像(memo の アバター)は outline を --c-border から --c-sub へ。 color: var(--c-sub)&:hover { color: var(--c-text) }
- リスト行・
カードの hover 背景は --c-surface の 50% 透過で 固定 8 箇所で
完全一致。 ベタ塗りの --c-surface は メニュー項目など 「明確に 選択する」 要素だけに 使う。 background-color: oklch(from var(--c-surface) l c h / 50%)
- popover / dialog の
開閉は 入り口と 出口の 両端を 必ず対で 宣言する @starting-style と
:not( [open] ) / :not(:popover-open) を 組にし、 display と overlay を allow-discrete で 0.2s。 移動量は popover 4px、 dialog 8px。 @starting-styletransition-behavior: allow-discretetranslate: 0 4pxtranslate: 0 8px
- ループする
アニメーションは スピナーの 回転だけ 1s linear infinite。
装飾目的の 無限アニメーションは 持たない。 animation: spin 1s linear infinite
- スクロール駆動アニメーションは
端フェードの マスクにだけ使う 時間ベースの
演出には 使わない。 横スクロールする テーブルと カテゴリナビの 2 箇所。 - prefers-reduced-motion では
transition を 潰し、 animation-duration は 触らない 共有 base.css で
一括。 唯一の @keyframes は スクロール 駆動の マスクなので animation は 残す。 装飾アニメは 局所で opt-in する (art の view transition)。 transition-duration: 0.01ms !importantscroll-behavior: auto !important
Links 5 rules
- chrome(ナビ・
フッター・ メタ)の リンクは 既定で 下線なし。 hover で 下線か 色変化 a { color: inherit; text-decoration: none }
- 本文リンクは
色付き + text-underline-offset。 下線の 太さは 1px prose は
--c-link、 トップページは 本文色の まま 下線色だけ --c-sub の 50% に 落とす。 text-decoration-thickness: 1pxtext-underline-offset: 4px
- 外部リンクは
target=_blank + rel=noreferrer noreferrer は
noopener を 含意するので、 rel は この 一語で 足りる。 target="_blank" rel="noreferrer"
- visited 色を
変えるのは prose 本文だけ --c-link-visited の
利用箇所は prose.css のみ。 それ以外は visited でも 色を 据え置く。 --c-link-visited
- 外部リンクは
末尾に move-up-right アイコンを インラインで 添える 0.625〜0.75rem、
vertical-align -0.05em。 width: 0.625remvertical-align: -0.05em
Typography 8 rules
- 見出しは
共有 base で palt + text-wrap: balance。 本文側の palt は .palt で 明示付与 font-feature-settings: "palt"text-wrap: balance.palt
- 文字サイズは
小さい 側に 寄せる。 本文は --fs-sm、 --fs-base 以上は 滅多に 使わない --fs-xs 約 60 箇所、
--fs-sm 約 43 箇所、 --fs-2xs 約 24 箇所に 対し、 --fs-base 以上は 5 箇所。 --fs-sm--fs-xs--fs-2xs
- 数字が
並ぶ箇所は tabular-nums を 付ける 日付・件数・ランク・年号・ページ番号。
全アプリに 分布。 日付の 書式は YYYY.MM.DD。 font-variant-numeric: tabular-numsYYYY.MM.DD
- 日付・
キャプション・ページャの 数値・トークン名は --font-mono font-family: var(--font-mono)
- 日本語の
改行は BudouX。 CSS 側は .budoux の 2 プロパティだけ <Budoux> で
ラップするか、 me のように rehype で 本文へ 自動適用する。 word-break: keep-alloverflow-wrap: anywhere
- text-wrap: pretty は
使わない 長い
CJK 本文で Chrome の レンダラが 落ちる ため、 prose では 明示的に 回避している。 - letter-spacing は
原則 0。 例外は mono キャプションの 0.02em letter-spacing: 0.02em
- 行送りは
UI が --lh-sm、 読み物が --lh-base、 prose 本文だけ 2 --lh-sm: 1.5--lh-base: 1.75line-height: 2
Spacing & shell 7 rules
- body グリッドの
行名は [top] auto [main-start] 1fr [main-end] auto [bottom] 共有ヘッダー・
フッターは この 行名に 自分を 配置する。 grid-template-rows: [top] auto [main-start] 1fr [main-end] auto [bottom]
- 本文カラムは
640px 未満で padding-inline 1rem、 640px 以上で 0 42rem を
フルに 使う。 padding-inline: 1rem@media (width >= 640px) { padding-inline: 0 }
- ブレークポイントは
640 / 768 / 1280px の 3 段。 range syntax で 書く (width >= 640px)(width >= 768px)(width >= 1280px)
- 右サイドバー(目次)は
1280px 以上でのみ fixed で 出す position: fixedtop: 13remright: 2rem
- 縦リズムは
--space-1 / 2 / 3 を 最小単位に、 セクション間は --space-8〜12 margin / padding / gap で
トークンと 一致する 値は var(--space-*) で 書く。 0.125rem や 2.5rem のように トークンの ない 値だけ生 rem。 --space-1: 0.25rem--space-3: 0.75rem--space-8: 2rem--space-12: 3rem
- 幅の
上限は 用途別に 3 種: 読み物 42rem、 画像 1440px、 ドキュメント 84rem --content-width: 42remmin(100% - 5rem, 1440px)--site-width: 84rem
- コンテナクエリは
局所レイアウトにだけ使う メディアクエリの
代替ではない。 alert ブロックと design の ステージ列。
Images 8 rules
- blur-up は
プレースホルダを 背景に 敷き、 .image-loaded で blur(36px) を 解く getImage({ width: 20, height: 11, format: "avif" })filter: blur(36px)
- 解除ハンドラは
onload と onerror の 両方を インライン属性で 付ける 共有ヘルパ blurLoadHandlers を
使う。 blurLoadHandlers(".blur-load")
- JS 無効時は
noscript で ブラーを 解く。 全レイアウトが BlurLoadNoscript を 置く - 配信形式は
avif(写真は webp 併記)、 quality 80、 widths + 明示 sizes format="avif"quality={80}widths={[...]}sizes="..."
- 遅延読み込みは
loading=lazy と decoding=async を 対で。 優先画像は eager + fetchpriority=high loading="lazy" decoding="async"loading="eager" fetchpriority="high"
- CLS 回避の
ため aspect-ratio を CSS 変数で 流し込む aspect-ratio: var(--aspect-ratio)
- 装飾画像と、
キャプションが 本文を 持つ画像は alt を 空に する 二重読み上げを
避ける。 me の 図版は キャプションと alt が 同文の ときだけ空に する (figureAlt)。 alt=""
- アバターは
円形 + 内側 1px の outline border-radius: 50%outline: 1px solid var(--c-border)outline-offset: -0.5px
Interaction & focus 10 rules
- フォーカスリングは
1 種。 クリップされる 要素だけ負の offset で 内側に 描く full-bleed な
行や カードなど 7 箇所で 同じ意図。 outline-offset: calc(-1 * var(--ring-width))
- スクロールバーは
ページ 8px、 内側スクローラ 3px + --c-sub。 Firefox は @supports で 分岐 ::-webkit-scrollbar { width: 3px }@supports (-moz-appearance: none)
- モーダルと
メニューは JS を 書かず popover / dialog + invoker commands で 開く command="show-modal"commandforpopovertargetclosedby="any"
- アンカーポジショニングには
必ず @supports not の フォールバックを 添える @supports not (anchor-name: --x)@position-try
- テキスト入力は
16px 未満に しない iOS が
フォーカス時に ズームするのを 防ぐ。 font-size: max(16px, var(--fs-base))
- 内側スクローラには
overscroll-behavior: contain を 付ける overscroll-behavior: contain
- カード全体
クリックは data-href + 親の イベント委譲。 選択中テキストと 内部リンクは 除外 data-clickabledata-href
- クリック可能な
カードは user-select: none - ボタンは
共有 .btn と 3 つの variant。 個別実装は chrome の 丸アイコンボタンだけ height: 2.25rem--radius-md--fs-sm--fw-medium1.5rem の丸ボタン
- 無効状態は
opacity + pointer-events: none opacity: 0.5opacity: 0.25pointer-events: none
Icons 4 rules
- アイコンは
Lucide 由来の 線画。 属性は 定型 viewBox="0 0 24 24"fill="none"stroke="currentColor"stroke-width="2"stroke-linecap="round"
- ブランドアイコンだけ fill=currentColor + 独自 viewBox
- 表示サイズは
0.875rem / 1rem / 1.125rem の 3 段 - 装飾アイコンは
aria-hidden、 意味を 持つものは 親に aria-label
Feedback & loading 5 rules
- 検索の
状態表示は p [role=status]。 空の ときも sr-only で 残し、 ライブリージョンを 外さない - 検索結果の
文言は 2 アプリで 同一 「{query}」に一致する〜はありません{total} 件中 {shown} 件を表示
- 無限スクロールは
IntersectionObserver + rootMargin 200px。 スピナーは 最低 500ms 出す 終端と
失敗は テキストで 告知する。 rootMargin: "200px"500ms
- ローディング枠は
grid 中央寄せ、 padding 2rem、 --fs-xs - 空
状態は 1 行の 日本語テキストだけ。 イラストや アイコンは 置かない
Head & meta 7 rules
- meta color-scheme を
CSS より 先に 置く ダーク環境での
白フラッシュ防止。 同じコメントが 5 アプリに ある。 lang は 文書の 言語に 合わせる (art / lgtm は en)。 <meta name="color-scheme" content="light dark">
- favicon は
4 点セット favicon.ico sizes="32x32"icon.svgapple-touch-icon.pngmanifest.webmanifest
- Web フォントは
読まない。 本文は system-ui、 Inter は OG 画像専用 font-family: system-ui, sans-serif
- OG カードは
1200×630、 左下寄せ、 padding 80px、 fontSize 70px - description は
200 字に 切る。 既定値を どの アプリも 上 書きしない truncateDescription
- Umami は
websiteId と domains を 対で 渡す。 イベント名は kebab-case、 計測対象は ナビと 検索だけ data-umami-eventdata-umami-event-label
- JSON-LD は
script [is:inline] [type=application/ld+json] に set:html で 流す
Dark mode 4 rules
- ダークの
境界線は 色ではなく 不透明度で 作る。 ライトは グレーの 実色 oklch(var(--uchu-yang-raw) / 10%)
- 影と
グローは ダークでだけ効かせ、 ライトでは 透明に 落とす - 外部由来の
画像(favicon)は ダークでも ライト地を 敷く - box-shadow は
原則 --shadow-sm だけ。 多層の 落ち影は モーダルと ポップオーバーの 3 箇所に 限る 0 14px 28px -6px0 2px 4px -1px
Other patterns 5 rules
- リセットは
kiso.css を @layer reset で 読む。 import 4 行の 順序は 全アプリ共通 - アプリ固有の
色は @layer tokens 内で --c-* を 追加する コンポーネントから
--uchu-* を 直接 使わない。 --c-star--c-faint--c-bg-gray--c-codeblock-bg
- アンカーの
飛び先には scroll-margin を 置く scroll-margin: 3remscroll-margin-top: 1.25rem
- サーフェスに
載せる 小アイコンは 2〜2.5rem 角、 --radius-md、 --c-surface、 --shadow-sm - 行数を
切る ときは text-overflow ではなく -webkit-line-clamp -webkit-line-clamp: 2-webkit-line-clamp: 1
全 72 項目。