SuCSS

Just HTML, already styled.

SuCSSはクラスレスのCSSフレームワークです。<header>、<table>、<button> のような素のHTMLを書くだけで、リキッドグラスの見た目とダークモードとアクセシビリティがついてきます。クラス名も、カスタム属性も、ビルドも要りません。

このページ自体が見本です。class 属性は1つも使っていません。下の1行を <head> に貼れば、あなたのHTMLもこう表示されます。

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ryo9ra/su-css/dist-lib/sucss.min.css">

su(素)は「飾りのない、そのままの」という意味です。

特徴

クラス名を書かない

スタイルは要素そのものから決まります。命名規則を決める必要も、ユーティリティクラスを覚える必要もありません。

ダークモード標準

prefers-color-scheme に追従します。data-theme を付ければどちらかに固定できます。

アクセシブル

WCAG AA/AAA のコントラスト、見える :focus-visible リング、十分なタップ領域。prefers-reduced-motion と prefers-reduced-transparency にも従います。

リキッドグラス

面はすりガラスではなくレンズです。背景が透けたまま色がにじみ、縁には光の線が乗ります。

1ファイル・依存ゼロ

CSSが1枚だけ。minify + gzip で 6.6 KB。JavaScriptもビルドステップもありません。

変数で調整

色も角丸もリズムもCSSカスタムプロパティです。--hue を変えればページ全体が動きます — カスタマイザーで試す。

インストール

CDNから読み込むなら、上の1行をそのまま <head> に貼ります。バンドラを使うならnpmから入れられます。

npm install @ryo9ra/su-css
import '@ryo9ra/su-css/sucss.min.css';

配布ファイル
読み込み指定 ファイル サイズ
@ryo9ra/su-css dist-lib/sucss.css 64 KB (gzip 16.1 KB)
@ryo9ra/su-css/sucss.min.css dist-lib/sucss.min.css 36 KB (gzip 6.4 KB)

対象ブラウザは Chrome/Edge 111+、Safari 16.4+、Firefox 128+ です。backdrop-filter のないブラウザでは、面が半透明ではなくベタ塗りになります。

要素見本

ここから下も class 属性を1つも使わない素のHTMLです。ページのソースを開いて見比べてみてください。

テキスト

段落は --line-height と --max-width にしたがって組まれます。強調、強調(斜体)、太字、イタリック、下線、取り消し線、インラインコード、Ctrl + C、CSS、x、出力、定義語、下付き、上付き、そして リンク がそのまま揃います。

クラス名の設計に費やしていた時間は、そのままセマンティクスの設計に回せる。

— 素のHTMLに戻るという発想

見出しレベル4

見出しレベル5
見出しレベル6

ランドマークロール

ランドマークはタグだけでなくロールでも受けます。banner、navigation、main、complementary、contentinfo、region、search を <div> に付けたマークアップも、対応する要素と同じ見た目になります。

<div role="complementary">

上の <aside> を描いているルールがそのまま適用されます。クラスも追加の CSS もありません。

<search>

リスト

  1. CSSを1行読み込む
  2. 素のHTMLを書く
  3. 手順は以上です
クラスレス
class属性を使わず、要素セレクタだけでページを組み立てる方式。
リキッドグラス
背景を透かしつつ色をにじませ、縁に光を乗せる面の表現。

表

主要なカスタムプロパティと既定値
プロパティ 既定値 効くところ
--hue 262 アクセント色と背景メッシュ
--sat 85% アクセント色の彩度
--max-width 860px 本文カラムの幅
--radius 1.375rem 面の角丸
--glass-blur 14px ガラスが背景をぼかす量
すべて :root で上書きできます。
並べ替えできる表。指標は見出しセルの aria-sort に従います
要素 属性 示すもの
<details> [open] 開閉の状態
<progress> [value] 進捗の度合い
<th> [aria-sort] どの列で並べ替えたか

1列目に aria-sort="ascending" が付いているので、指標は上を向きます。"descending" なら下向きです。2列目は aria-sort="none"、3列目は属性なしで、どちらもこれまでどおりに描かれます。

フォーム

アカウント
設定

ボタンと状態


ツールバーは Tab で一度入り、矢印キーで中を移動する操作を伴います。SuCSS が描くのはバーの見た目までで、そのキーボード操作は @ryo9ra/su-css/behaviors.js が書くか、自分で書くかです。どちらもできない場合は role="group" を使ってください。

安定版 MIT 依存ゼロ

70%

42%

折りたたみ(details / summary)

開閉する領域も要素だけで組めます。

ダイアログ

<dialog> と背面のバックドロップ、どちらにもスタイルが当たっています。

JavaScript なしの開閉

popovertarget を持つボタンは、スクリプトなしで [popover] パネルを開閉します。クリックの配線はブラウザがやります。やらないのは、ボタンの aria-expanded を揃えることです — それをタダでやってくれるのは <details> だけです。指標はその属性だけから描いていて、aria-sort と同じく文字ではなく境界線です。フォントもフォールバックも要りません。このページでは、この属性を揃えるためだけの短いスクリプトを走らせています — CSS 側はそのスクリプトの存在を前提にしていません。

このパネルは [popover] です。ガラスの見た目は <dialog> と共有していて、重複させていません。何をあえて外しているかは CSS 側を見てください。

[popover] をまだ知らないブラウザでは、このパネルはただのブロックとして常時開いたまま表示されます。これは新しい属性にスタイルを当てる代償として許容しています。ここでは :popover-open を使っていないので、宣言しているブラウザ対象は動きません。

role="tablist" — タブ

選ばれたタブは aria-pressed と同じ塗りで描かれます — role="tab" が aria-selected を持つだけの違いです。トグルボタンと違うのは挙動のほうで、タブは矢印キーで移動した先がそのまま選択になり、対応する role="tabpanel" が切り替わります(WAI-ARIA APG の automatic activation)。

選択中のパネルの中身。role="tabpanel" は tabindex="0" を持ち、中の要素を一つずつ Tab で辿らなくても、パネルそのものをひとつの停止点として読めます。

タブの見た目は CSS だけで完成しますが、矢印キーでの選択移動とパネルの表示切替は @ryo9ra/su-css/behaviors.js が担当します。読み込まなければ、選択が最初のタブに固定されたまま動かない、見た目だけのボタンの並びになります。

操作できないもの

disabled はフォーム部品の属性で、リンクには付けられません。aria-disabled="true" なら何にでも同じことを言えます。SuCSS は同じ濃度で淡くします。

次の手順へ

どちらもタブ順からは消えません。Tab はいまも両方に止まるので、スクリーンリーダーがたどり着いて「なぜ無効なのか」を読み上げられます。SuCSS は pointer-events: none を付けません。クリックを止めるのはハンドラの仕事で、ここで奪うと CSS では返せなくなります。

この手順はまだ開始できません

領域に inert を付けると、その中のすべてがフォーカス・クリック・選択の対象から外れます。ブラウザはそこまでやって見た目は何も変えないので、淡くするのはスタイルシートの担当です。

上のパネルは Tab がまるごと飛ばします。そちらはブラウザの担当です。

読み込んでいるもの

aria-busy="true" は、その要素の中身がまだ確定していないという作者の宣言です。支援技術はすでにこれを読むので、同じことをクラスなしで画面にも出せます。ボタンはラベルの前に、それより大きい領域は角に指標が出ます。角のものはフローの外にあるので、注釈している中身を押しのけません。

最近のデプロイ

直近 10 件を取得しています…

busy は disabled ではありません。上のボタンは押せます。aria-busy が言っているのは「答えがまだ来ていない」であって「この操作は今できない」ではないからです。押させたくなければ disabled を併記します。prefers-reduced-motion では輪が回転を止めてその場に残るので、動きが消えても状態が形として残ります。

立ち止まって読ませるメッセージ

上の role="status" がバッジなのは、状態がひと言で済むからです。文章になるならブロックにします — 不具合は role="alert"、知っておくとよいことは role="note"。

直前のデプロイが完了しませんでした。公開はされておらず、ひとつ前のバージョンが動いています。

--hue を変えると、アクセントだけでなく背景のグラデーションも一緒に動きます。

この2つしかないのは、ロールで表せる深刻度がこの2つしかないからです。「成功」や「注意」はどちらなのかを class で言う必要があり、このフレームワークにはそれがありません。

コードとメディア

コードブロックはページを押し広げず、横にスクロールします。<figure> と <figcaption> の組み合わせにも対応しています。

<article>
  <h2>No classes needed</h2>
  <p>Write the element and it is styled.</p>
</article>
SuCSSのカバーアート
ブランドアセットは assets/brand/ にあります。

カスタマイズ

色・角丸・余白・書体はすべてカスタムプロパティです。:root に数行足せばページ全体が動きます。カスタマイザーでつまみを動かしながら決めることもできます。

:root {
  --hue: 158;          /* accent colour and background mesh */
  --sat: 70%;
  --radius: 0.5rem;    /* calmer corners */
  --max-width: 72ch;   /* width of the body column */
  --base-size: 1rem;
  --font-sans: "Inter", sans-serif;
}

テーマを固定する

既定ではOSの設定に追従します。<html> の data-theme で固定できます。このページ上部のボタンがやっているのもそれだけです。

<html data-theme="dark">   <!-- always dark -->
<html data-theme="light">  <!-- always light -->
<html>                     <!-- follow the OS (default) -->