GitHubのトレンドに meta/astryx が載っていました。6位、スターは14,122で、その日だけで198増えています。説明は「An open source design system that’s fully customizable and agent ready」。README によると Meta の社内で8年育ったデザインシステムで、部品は150以上、ライセンスは MIT、まだベータ表記です。
気になったのは、README に約束が並んでいたことです。ビルドプラグインは要らない、部品はアクセシブル、人と AI エージェントが同じ作法で使える。受託の検収でライブラリを受け入れる立場だと、こういう言葉はそのまま信じず、確かめられる形に直したくなるんですよね。
テストは目的ではなく手段です。知りたかったのは「日本語の業務画面の土台として、どこまで任せてよいか」でした。そこで、触る前に受け入れ基準を決めました。
- 変換を確定する Enter や、変換を取り消す Esc で、送信や選択や閉じる動作が走らない
- 日本語の文言とカレンダーが出る
- プラグインなしで動き、
classNameで上書きできる - CLI が出すページテンプレートが、そのまま動く
- テーマを変えても文字が読める
先に結果を書くと、1つ目はきれいに通りました。4つ目が想定外でした。
試した環境と、最初の数分
使い捨ての Docker コンテナ(node:22-bookworm、メモリ4GB・2CPU)の中で、Vite の React テンプレートに README の手順で入れました。Node.js 22.23.1、npm 10.9.8、React 19.3.1、Vite 8.3.6、TypeScript 7.0.2、Astryx は v0.7.10(コミット 2b827d6、2026-11-06 UTC 公開)です。ブラウザの検査はコンテナ内の Playwright 1.65.0 と axe-core 4.14.0、記事のキャプチャは Windows の Chrome 155 で撮りました。
# 雛形を作る
$ npm create -y vite@latest app -- --template react-ts --no-interactive # 0.6秒
$ cd app && npm install --no-audit --no-fund # 9.9秒・28パッケージ
$ du -sh node_modules
96M node_modules
# README のとおりに入れる
$ npm install --no-audit --no-fund @astryxdesign/core @astryxdesign/theme-neutral @stylexjs/stylex # 3.3秒・16パッケージ
$ npm install --no-audit --no-fund -D @astryxdesign/cli # 3.0秒・111パッケージ
$ du -sh node_modules
311M node_modules
$ npx --no-install astryx component --list
Next step: run `npx astryx init` to finish setup and install the Astryx agent prompt.
Components (174)
(以下、部品の一覧)
--no-install を付けたのは、README に「scoped でない astryx は、CLI を依存に入れるまで無関係な npm パッケージに解決される」と注意書きがあったからです。部品数は174。AvatarGroupOverflow のような子部品も1件に数えた数ですが、「150以上」はそのとおりでした。
動かすまでは本当に早かったです。CSS を3行 import して Theme で包むだけで、Button が出ました。
Button 1個で CSS は 283 kB
「プラグイン不要」の代わりに何を払っているのかを、Vite のビルド結果で見ました。
| 構成 | JS(gzip) | CSS(gzip) |
|---|---|---|
| React だけ(対照) | 219.47 kB(68.58) | なし |
| Theme だけ(CSS は読まない) | 308.75 kB(96.27) | なし |
| Button だけ(Theme も CSS も読まない) | 364.49 kB(115.48) | なし |
Button + Theme(@astryxdesign/core/Button から import) | 451.25 kB(142.19) | 283.64 kB(44.17) |
Button + Theme(@astryxdesign/core から import) | 451.24 kB(142.11) | 283.64 kB(44.17) |
| Button + Theme(StyleX で部品もビルド。後述) | 454.49 kB(142.29) | 78.34 kB(13.86) |
Button を使う構成には、このほかに Tooltip の別チャンク(約2 kB)が付きます。
分かったことは3つです。どこから import しても JS はほぼ同じで、tree-shaking は効いています。違いは変換モジュール数(2,215 と 3,062)とビルド時間(298ms と 470ms)だけでした。一方で CSS は、Button 1個でも全部品の分が載ります。そして StyleX のコンパイラを入れて部品ごとビルドすると(リポジトリの apps/example-vite の構成です)、CSS は 78.34 kB まで減り、ビルドは 2.33 秒に増えました。
ここは好みが分かれるところですが、社内の管理画面なら gzip で 44 kB を払って設定ゼロ、は悪くない取引だと思います。
変換中の Enter で、何も起きないか
日本語の業務画面でまず確かめたいのがここです。変換を確定した Enter でフォームが送信されたら、それだけで不具合票が立ちます。リポジトリ直下には IME_GUARD_DESIGN.md という設計メモがあり、TextInput の onEnter の説明にも「IME-safe」とあります。書いてあることは、測ります。
本物の IME はコンテナに入れていません。Chromium の DevTools プロトコルで変換中の状態を作り、キーイベントを送りました。
// 部品1つ x キー1つごとに、ページを開き直す(抜粋)
await cdp.send('Input.imeSetComposition', {text: 'にほんご', selectionStart: 4, selectionEnd: 4});
// 変換中に押されたキー。キー名はそのままで、keyCode は 229
await cdp.send('Input.dispatchKeyEvent', {type: 'rawKeyDown', key: 'Enter', code: 'Enter', windowsVirtualKeyCode: 229, nativeVirtualKeyCode: 229});
await cdp.send('Input.insertText', {text: '日本語'}); // 確定
// 確定のあとに、同じ keydown をもう一度送る。最後に page.keyboard.press('Enter')
// ページ側で記録した keydown
// 変換中: key=Enter keyCode=229 isComposing=true
// 確定後: key=Enter keyCode=229 isComposing=false
正直に書くと、これはブラウザに届くイベントを再現しただけです。MS-IME や macOS の日本語入力の実機、Safari では確かめていないので、この検証にはまだ改善の余地があります。
大事なのは対照です。素の <input> に e.key === 'Enter' だけを見るハンドラを付けたものを同じ手順で叩き、この再現で事故が起きることを先に示しました。
| 部品 | キー | 変換中 | 確定後の 229 | 本物のキー |
|---|---|---|---|---|
| 素の input(対照) | Enter / Esc | 発火 | 発火 | 発火 |
| TextInput(onEnter) | Enter | 何も起きない | 何も起きない | 発火 |
| ChatComposer(onSubmit) | Enter | 何も起きない | 何も起きない | 発火 |
| Typeahead / Tokenizer | Enter | 何も起きない | 何も起きない | 候補を選択 |
| Selector / MultiSelector(hasSearch) | Enter | 何も起きない | 何も起きない | 下矢印のあと Enter で選択 |
| CommandPalette | Enter | 何も起きない | 何も起きない | 下矢印のあと Enter で選択 |
| Typeahead / Tokenizer / Selector / MultiSelector | Esc | 閉じない | 未計測 | 閉じる |
| CommandPalette / Dialog / Drawer / Popover | Esc | 閉じない | 閉じない | 閉じる |
対照の素の input は3回とも発火しました。Astryx の10部品は、変換中と確定後の 229 では動かず(表の未計測を除く)、本物のキーでは動きます。「キーが死んでいるだけ」ではないことも、これで言えます。変換候補を選ぶつもりの下矢印でも aria-activedescendant は動かず、確定後の入力値はどの部品も 日本語 のままでした。
実装は utils/ime.js の isImeKeyEvent で、isComposing と keyCode === 229 の両方を見ています。設計メモが棚卸ししていた箇所は7つでしたが、0.7.10 の配布物では25ファイルがこの判定に触れていました。
正直なところ、ここが一番よい意味で予想を裏切られました。受け入れ基準の1つ目は、再現した範囲で合格です。
和暦は出る。日本語カタログは6割
InternationalizationProvider に ja-JP のカタログを渡し、Calendar に calendar="japanese" を付けて並べた画面です。カードの見出しと入力値は私が書いたものです。

カレンダーの見出しは「令和8年11月」で、スクリーンリーダー向けのラベルも「令和8年11月7日土曜日、選択済み」でした。「任意」「ファイルを選択」「カレンダーを開く」「3 分前」も日本語です。ただし、右上の複数選択を開いて検索欄に「総務」と入れると、1 selected と Create "総務" は英語のままでした。
カタログを数えると、英語の 614 キーに対して ja-JP は 365 キーです。足りないのは comments が66、powersearch が35、richTextEditor が29、diffViewer が22、tokenizer が19 など。29 言語のどれも 365 キーだったので、日本語だけが遅れているわけではありません。文言の上書きは docs internationalization/i18n-library-integration に案内があるので、足りない分は自前で補う前提で見積もるのが安全です(上書きそのものは試していません)。
細かいところも3つ。ja-JP を渡しても <html lang> は Vite 雛形の en のままでした。neutral テーマの font-family は Figtree から始まって和文フォントの名前がなく、Web フォントも読み込まれない(document.fonts は0件)ので、日本語の字形は OS 任せです。CLI のドキュメントは --lang が英語、中国語、dense の3つで、astryx search "日付" は No results for "日付". でした。
className は効く。Tailwind は1行で結果が変わる
「No styling lock-in」も確かめました。素の CSS でも CSS Modules でも、className に渡した background-color と border-radius はそのまま勝ちます。Astryx のスタイルが @layer astryx-base の中にあり、レイヤーの外の CSS のほうが強いからです。さらに、hover の表現は background-image の薄いグラデーションだったので、背景色を変えても hover の反応は残りました。うまい作りだと思います。
Tailwind 4.3.5 は README に専用の手順があり、先頭に @layer の1行があります。この1行を省いて、ふつうに import するとどうなるかを比べました。
/* README の Tailwind 手順の先頭 */
@layer reset, theme, base, astryx-base, astryx-theme, components, utilities;
/* ブラウザで読み取ったレイヤーの順序(右ほど強い) */
/* README の順序 : reset, theme, base, astryx-base, astryx-theme, components, utilities */
/* Tailwind を先に import: theme, base, components, utilities, reset, astryx-base, astryx-theme */
/* Astryx を先に import : reset, astryx-base, astryx-theme, theme, base, components, utilities */

3つのビルドを撮って切り出し、縦に並べて左の説明を足した図です。右端の bridge は README の tailwind-theme.css を確かめるために置いた要素で、これを読んでいる上段だけ rounded-lg が 12px になります。
Tailwind を先に読むと utilities が Astryx より弱くなり、bg-red-500 は負けて背景は rgb(27, 27, 27) のままでした。Astryx を先に読むと今度は Tailwind の base が勝ち、何も指定していないボタンの背景は透明に、padding-left は 0px になります。どちらもビルドは通るので、画面を見るまで気づけません。README どおりなら問題ないので、これは不具合ではなく「その1行は飾りではない」という話です。
テンプレート15本が、ビルドは通るのに白画面
astryx init は 0.4 秒で AGENTS.md(2,093バイト)と GEMINI.md を書き出します。中身の方針は「start every page from a template, never from scratch」。人にも AI エージェントにも、まずテンプレートから作れと勧めています。ならばテンプレートが動くかどうかは、受け入れ基準に入れるべきです。
CLI のテンプレートはページ57本、ブロック710本ありました。ページ57本を全部書き出して tsc にかけると、recharts と @heroicons/react が無いという TS2307 が65件出ます。これは入れれば0件になりますし、パスを指定して書き出せば CLI が入れるべきものを教えてくれます。問題はブラウザで開いたあとでした。57本中15本が真っ白です。
$ npx --no-install astryx template shell-top-nav ./src/page.tsx
Copied template to src/page.tsx
(デモ画像の置き換えの案内は省略)
This template uses StyleX. Your project needs a StyleX compiler plugin - see `npx astryx docs styling-overview` for setup steps.
$ npx tsc -p tsconfig.app.json --noEmit # rc=0
$ npx vite build --outDir /tmp/crash-dist --emptyOutDir # rc=0
$ npx --no-install astryx doctor --built . # rc=0
pass: 10
warn: 2
fail: 0
info: 11
No failures - but review the [warn] warnings above when you can.
# ブラウザで開くと、#root の子要素は0個、body の文字数は0
Error: Unexpected 'stylex.create' call at runtime. Styles must be compiled by '@stylexjs/babel-plugin'.
型検査もビルドも診断コマンドも通って、画面だけが何も出ません。
「動いた=品質OK ではない」と普段から言っていますが、今回は「ビルドが通った=動いた」ですらありませんでした。
原因は単純で、この15本は @stylexjs/stylex を直接 import して、自分の中で stylex.create を呼んでいます。README の「No build plugins needed」は Astryx の部品の話で、テンプレートが自前で書いた StyleX は別なんですね。該当するのは dashboard- で始まる5本(comparison / scorecard / cohort-funnel / progress / alert-rail)、table- で始まる5本(collapsible / comparison / filter / inbox / tree)、それに canvas-editor、kanban-board、settings-dialog、shell-top-nav、work-item-detail です。dashboard、settings、login、table-page などの残り42本は、プラグインなしでも実行時エラーなしで開けました。
CLI も黙っているわけではありません。上のとおりパスを指定して書き出せば警告が出ますし、astryx swizzle Button にも同じ趣旨の警告があります(swizzle の実行時の失敗は試していません)。doctor の warn にも src/page.tsx:44 a stylex.create declaration と出ますが、これは「部品の prop やトークンを先に使おう」という書き方の助言でした。パスなしで標準出力に書き出す astryx template canvas-editor では、この警告は見当たりませんでした。
もう1つ気になったのは、AGENTS.md が最初に勧める astryx build です。astryx build "invoice list page" も astryx build "請求書の一覧ページ" も、出発点に shell-top-nav を挙げました。警告は添えられるものの、README の手順どおりの環境だと、勧められた1本目がそのまま白画面になります。
では、何を足せば動くのか。警告が案内する docs styling-overview の表から順に試しました。
| 構成 | vite build | 画面 | メモ |
|---|---|---|---|
| README のまま(コンパイラなし) | rc=0 | 真っ白 | CSS 283.64 kB |
@stylexjs/rollup-plugin をそのまま(表が Vite 向けに案内) | rc=1 | なし | index.css を Babel が読み、decorators の SyntaxError |
同じプラグインに enforce: ‘post’(私の回避策) | rc=0 | 表示 | 書き出された stylex.css(0.19 kB)は HTML から読まれない |
@stylexjs/unplugin の stylex.vite()(astryx.css は残す) | rc=0 | 表示 | CSS 283.84 kB |
@astryxdesign/build の astryxStylex()(リポジトリの apps/example-vite の構成) | rc=0 | 15本すべて表示 | Button だけの例で CSS 78.34 kB |
上の4行は shell-top-nav 1本で、5行目だけ15本全部で確かめています。ここでの「表示」は、実行時エラーがなく本文の文字が出たという意味で、見た目の差までは比べていません。Vite 8.3.6 で試した結果なので、Vite の版によって2行目の失敗は変わるかもしれません。
CSS が減る5行目には注意が1つあります。docs styling/tokens-and-setup には「astryx.css は読み続け、node_modules/@astryxdesign をコンパイル対象に入れない」とあり、リポジトリの例とは方針が違います。なので5行目は、案内された手順というより例に倣った実験です。私が手元に残すなら、astryx.css を残したまま足せる次の構成です。
// vite.config.ts
import {defineConfig} from 'vite';
import react from '@vitejs/plugin-react';
import stylex from '@stylexjs/unplugin';
export default defineConfig({plugins: [stylex.vite(), react()]});
axe で見えた範囲
「アクセシブル」も、機械で測れる範囲だけ測りました。ページテンプレート57本を neutral テーマのライトで開き、axe-core の WCAG 2.x A/AA のルールにかけています。StyleX の15本はコンパイラありの版で測りました(自作の index.html に title を書き忘れたせいで出た document-title は除いています)。
違反なしは45本、残り12本で合計51か所でした。多い順に、表示文字と aria-label がずれている label-content-name-mismatch が16か所、table-tree の aria-conditional-attr が13か所、dashboard-comparison の aria-hidden-focus が12か所、押す範囲が小さい target-size が8か所(table-page の絞り込みボタンは 12px 四方)、ほかに2か所です。
51か所のうち28か所は table-tree と dashboard-comparison の2本に集中しています。ただ、axe が見るのは機械で判定できる項目の、しかも初期表示だけです。違反0は合格の必要条件であって、十分条件ではありません。
テーマを変えると、読めなくなる組み合わせがある
7テーマのライトとダーク、代表の5本(login、settings、dashboard、table-page、form-wizard)で、コントラスト違反の数を合計しました。
| テーマ | ライト | ダーク | ライトで違反した色の例 |
|---|---|---|---|
| neutral / butter / stone / gothic | 0 | 0 | なし |
| chocolate | 127 | 0 | #B88859 と #fffcf7 で 3.06:1 |
| matcha | 127 | 0 | #707e46 と #f0f0e0 で 3.83:1 |
| y2k | 68 | 0 | #675d52 と #cccffa で 4.24:1 |
違反した文字色は、3テーマとも --color-text-secondary のライト側の値と一致します。小さめの補足文で使われていた色なので、このまま業務画面に載せるなら基準に照らして決めるところです。なお y2k の違反は body を塗ったときだけ出ます(塗らない白地では0件でした)。
実はこの表は測り直したものです。最初は何も塗らずに測り、ダークの全テーマで68〜176か所の違反が出て慌てました。ところが neutral のダークのスクリーンショットは、普通に読める暗い画面なんです。背景を何も塗っていないと axe は白として計算するらしく、body に --color-background-body を塗って測り直すとダークは全部0件になりました。今思えば、検査ツールの数字を画面と突き合わせなければ、誤った結論を書くところでした。
ただ、その調査で本物の問題も1つ見つかりました。gothic テーマです。

同じ settings テンプレートを mode="light" で撮った2枚を左右に並べ、上の帯の文字を足した図です。gothic は mode="light" でも色が暗いままで、見出しの文字は #e8f1f6 です。Quick Start の手順には body を塗る指定が無く、ブラウザの地は白なので、コントラストは 1.14:1 になります。mode を省略して OS がライトの場合も、見出しの色は同じで body は透明でした。body に --color-background-body を塗れば右のように読めますが、この1行を知らないと左の画面になります。
CLI はオフラインでも動く
docker network disconnect で通信を切って CLI を叩くと、component、search、docs、template、doctor、build、manifest、discover はすべて rc=0 でした。止まったのは blog だけで、Could not reach https://astryx.atmeta.com/rss.xml: fetch failed です。ドキュメントが 23MB のパッケージに同梱されているので、閉じた環境で AI エージェントに使わせるのにも向きそうです。
一方で、トークン節約をうたう --dense の効果は小さめでした。component Button は 10,029 バイトが 8,666 バイトに、component Typeahead は 8,680 バイトのまま変わりません。
試してみての所感
最初に決めた受け入れ基準に戻ります。
| 受け入れ基準 | 判定 | 根拠 |
|---|---|---|
| 変換中の Enter / Esc で動作が走らない | 合格(再現した範囲) | 10部品で0件、対照は発火 |
| 日本語の文言とカレンダー | 条件付き | 和暦は出る。カタログは 614 キー中 365 キー |
| プラグインなしで動き、className で上書きできる | 合格 | 部品について。Tailwind は README の順序が前提 |
| CLI のページテンプレートがそのまま動く | 不合格 | 57本中15本は StyleX コンパイラなしで白画面 |
| テーマを変えても読める | 条件付き | 3テーマのライトで補足文字が 4.5:1 未満。gothic は body の塗りが必須 |
私なら、日本語入力の多い社内の管理画面の土台として候補に入れます。IME の扱いが部品をまたいで揃っているのは、検収する側から見るとかなり大きいです。
入れるなら最初に3つやります。@stylexjs/unplugin を入れておく。body を --color-background-body で塗る。そして、ページを開いて pageerror が0件で本文に文字があることを見るスモークテストを1本、CI に足す。今回の白画面は、型検査でもビルドでも診断コマンドでも止まらなかったので、ブラウザで開く検査でしか受け入れ基準を守れません。
様子見なのは更新の速さです。v0.7.0 が10月13日、v0.7.10 が11月6日なので、24日で11リリース出ています。まだベータ表記でもあり、版は固定して、astryx upgrade --apply は検証環境で試してから当てるつもりです(今回はここまで試していません)。Next.js での動作、実機の IME、Safari も未確認なので、次に触るならそこからですね。