tui-designer について
React、SwiftUI、CSS、WebGLでレトロやサイバーパンク調のテキストUIを設計するスキルです。CRT効果、ネオン表現、配色、タイポグラフィ、実装上の注意点を扱います。
主な機能
- 01CRTとスキャンライン効果
- 02ネオン発光のスタイル
- 03レトロな配色とタイポグラフィ
- 04React Tuimorphicコンポーネントの指針
- 05SwiftUI MetalとWebの手法
- 06クロスプラットフォームの注意点と性能
活用シーン
- 01ターミナル風インターフェースの設計
- 02SwiftUIビューへのCRT効果追加
- 03ネオン調のReactまたはWeb UI構築
- 04配色と等幅フォントの選定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ ckorhonen/ claude-skills --skill tui-designerインストール後、Claude Code で/tui-designerを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/color-palettes.mdcopywriting-guide.mdcrt-effects-web.mddesign-patterns.mdmetal-shaders-ios.mdtuimorphic-reference.mdtypography-guide.mdscripts/generate_palette.pytemplates/crt-shader.metalneon-glow.cssreact-tuimorphic-starter.tsxswiftui-crt-view.swift詳細
TUI デザイナー - レトロ/サイバーパンク端末 UI エキスパート
CRT モニター、蛍光体の発光、走査線、サイバーパンクのネオンなど、本格的なレトロコンピューティングの美学を取り入れたテキストベースのユーザーインターフェースの設計と実装に関する専門的なガイダンスです。
このスキルを使う場面
次のような場合にこのスキルを使います。
- 端末風またはハッカー風の UI を作成する
- CRT モニターの効果(走査線、発光、樽型歪み)を実装する
- サイバーパンク/シンセウェーブ/レトロウェーブのインターフェースを構築する
- React で Tuimorphic コンポーネントを使用する
- SwiftUI でレトロ効果を実現する Metal シェーダーを実装する
- ネオンが光るテキストや UI 要素をデザインする
- レトロコンピューティングの美学に合うカラーパレットを選ぶ
- 等幅フォントや罫線素片を扱う
デザインの原則
レトロ/サイバーパンクの美学
この美学は、次の要素に由来します。
- CRT モニター:蛍光体の発光、走査線、画面の湾曲、ちらつき
- 端末インターフェース:等幅フォント、罫線素片、緑や琥珀色のテキスト
- サイバーパンク小説:ネオンカラー、暗い背景、高いコントラスト
- 1980 年代のコンピューティング:ASCII アート、限られたカラーパレット、ブロック状のグラフィック
ビジュアル要素のチェックリスト
- 暗い背景(ほぼ黒に、わずかな色味を加える)
- 全体に等幅フォントを使用する
- 枠やフレームに罫線素片を使用する
- テキストや枠、またはその両方にネオンの発光を加える
- 走査線の効果(控えめな水平線)
- 限られたカラーパレット(アクセントカラーは 1~3 色)
- テキストと背景のコントラストを高くする
- 任意:CRT の湾曲、色収差、ちらつき
クイックリファレンス:カラーパレット
蛍光グリーン(クラシックな端末)
| 役割 | Hex | 用途 |
|---|---|---|
| 明るい | #00ff00 | メインテキスト、強調表示 |
| 中間 | #00cc00 | サブテキスト |
| 暗い | #009900 | 薄暗い要素 |
| 背景 | #001100 | メイン背景 |
| 深い背景 | #000800 | パネル背景 |
サイバーパンクネオン
| 役割 | Hex | 用途 |
|---|---|---|
| シアン | #00ffff | メインのアクセント |
| マゼンタ | #ff00ff | サブのアクセント |
| エレクトリックブルー | #0066ff | 3 番目の色 |
| ホットピンク | #ff1493 | 警告 |
| 背景 | #0a0a1a | メイン背景 |
琥珀色 CRT
| 役割 | Hex | 用途 |
|---|---|---|
| 明るい | #ffb000 | メインテキスト |
| 中間 | #cc8800 | サブ |
| 暗い | #996600 | 薄暗い要素 |
| 背景 | #1a1000 | メイン背景 |
詳細な仕様は、color-palettes.md を参照してください。
タイポグラフィ
推奨フォント
Web:
font-family: 'GNU Unifont', 'IBM Plex Mono', 'JetBrains Mono',
'SF Mono', 'Consolas', monospace;
SwiftUI:
.font(.system(size: 14, weight: .regular, design: .monospaced))
罫線素片
Light: ─ │ ┌ ┐ └ ┘ ├ ┤ ┬ ┴ ┼
Heavy: ━ ┃ ┏ ┓ ┗ ┛ ┣ ┫ ┳ ┻ ╋
Double: ═ ║ ╔ ╗ ╚ ╝ ╠ ╣ ╦ ╩ ╬
Rounded: ╭ ╮ ╰ ╯
詳細なリファレンスは、typography-guide.md を参照してください。
コピーライティング
端末インターフェースには、簡潔で技術的、かつ権威ある独特の語り口があります。プロジェクトで指定されていない限り、以下を標準として使ってください。
基本原則
- 簡潔かつ明快に - すべての言葉に意味を持たせる
- 技術的な権威性 - システムは自分の役割を理解している
- 機械的な正確さ - 曖昧な表現、謝罪、埋め草を使わない
テキストの書式
| 要素 | 表記 | 例 |
|---|---|---|
| ヘッダー/タイトル | すべて大文字 | SYSTEM STATUS |
| ラベル | すべて大文字 | CPU USAGE: |
| ステータス表示 | すべて大文字 | ONLINE、OFFLINE |
| コマンド/入力 | 小文字 | > run diagnostic |
| 本文 | 文頭のみ大文字 | Connection established |
メッセージの接頭辞
[SYS] System message [ERR] Error
[USR] User action [WRN] Warning
[INF] Information [NET] Network
用語クイックリファレンス
| 操作 | 端末向け動詞 |
|---|---|
| 開始 | INITIALIZE、BOOT、LAUNCH、ACTIVATE |
| 停止 | TERMINATE、HALT、ABORT、KILL |
| 保存 | WRITE、COMMIT、STORE、PERSIST |
| 読み込み | READ、FETCH、RETRIEVE、LOAD |
| 削除 | PURGE、REMOVE、CLEAR、WIPE |
| 状態 | 端末向けの語句 |
|---|---|
| 作業中 | PROCESSING、EXECUTING、RUNNING |
| 完了 | COMPLETE、SUCCESS、FINISHED |
| 失敗 | ERROR、FAULT、ABORTED |
| 準備完了 | ONLINE、AVAILABLE、ARMED |
よく使うパターン
> INITIALIZING SYSTEM...
> LOADING MODULES [████████░░] 80%
> AUTHENTICATION COMPLETE
> SYSTEM READY
ERROR: ACCESS DENIED
ERR_CONNECTION_REFUSED: Timeout after 30s
WARNING: Low disk space (< 10%)
CONFIRM DELETE? [Y/N]
SELECT OPTION [1-5]:
避ける表現
- 「Please」、「Sorry」、「Oops」
- 「Just」、「Maybe」、「Might」
- 感嘆符の多用
- 絵文字(明示的に求められた場合を除く)
文体とトーンの完全なガイドラインについては、copywriting-guide.md を参照してください。
プラットフォーム:Tuimorphic を使用した React
Tuimorphic は、ターミナル風のアクセシブルな UI コンポーネントを 37 種類提供する React コンポーネントライブラリです。
クイックスタート
npm install tuimorphic
import { Button, Card, Input } from 'tuimorphic';
import 'tuimorphic/styles.css';
function App() {
return (
<div className="theme-dark tint-green">
<Card>
<h1>SYSTEM ACCESS</h1>
<Input placeholder="Enter command..." />
<Button variant="primary">EXECUTE</Button>
</Card>
</div>
);
}
テーマの設定
親要素に CSS クラスを指定してテーマを適用します。
// Dark mode with green tint
<div className="theme-dark tint-green">
// Light mode with cyan tint
<div className="theme-light tint-blue">
利用可能な色合い: tint-green、tint-blue、tint-red、tint-yellow、tint-purple、tint-orange、tint-pink
主なコンポーネント
| コンポーネント | 用途 |
|---|---|
Button | variant="primary|secondary|ghost" を指定するアクション |
Input | ターミナル風のスタイルを備えたテキスト入力 |
Card | 罫線で囲んだコンテナ |
Dialog | モーダルダイアログ |
Menu | ドロップダウンメニュー |
CodeBlock | 構文ハイライト付きコード |
Table | データテーブル |
Tabs | タブ形式のナビゲーション |
TreeView | ファイルツリーの表示 |
API の詳細については、tuimorphic-reference.md を参照してください。
ネオングローの追加
CSS グロー効果を使って Tuimorphic を強化します。
/* Neon text glow */
.neon-text {
color: #0ff;
text-shadow:
0 0 5px #fff,
0 0 10px #fff,
0 0 20px #0ff,
0 0 40px #0ff,
0 0 80px #0ff;
}
/* Neon border glow */
.neon-border {
border-color: #0ff;
box-shadow:
0 0 5px #0ff,
0 0 10px #0ff,
inset 0 0 5px #0ff;
}
/* Flickering animation */
@keyframes flicker {
0%, 100% { opacity: 1; }
50% { opacity: 0.95; }
52% { opacity: 1; }
54% { opacity: 0.9; }
}
.flicker {
animation: flicker 3s infinite;
}
プラットフォーム:Metal シェーダーを使用した SwiftUI
iOS 17 以降では、.colorEffect()、.distortionEffect()、.layerEffect() を使って、SwiftUI 内で Metal シェーダーを直接利用できます。
CRT 効果の実装
CRT.metal:
#include <metal_stdlib>
#include <SwiftUI/SwiftUI.h>
using namespace metal;
[[stitchable]] half4 crtEffect(
float2 position,
SwiftUI::Layer layer,
float time,
float2 size,
float scanlineIntensity,
float distortionStrength
) {
float2 uv = position / size;
// Barrel distortion
float2 center = uv - 0.5;
float dist = length(center);
float2 distorted = center * (1.0 + distortionStrength * dist * dist);
float2 samplePos = (distorted + 0.5) * size;
// Bounds check
if (samplePos.x < 0 || samplePos.x > size.x ||
samplePos.y < 0 || samplePos.y > size.y) {
return half4(0, 0, 0, 1);
}
// Sample color
half4 color = layer.sample(samplePos);
// Scanlines
float scanline = sin(position.y * 3.14159 * 2.0) * scanlineIntensity;
color.rgb *= 1.0 - scanline;
// Subtle color shift (chromatic aberration)
color.r *= 1.0 + 0.02 * sin(time * 2.0);
color.b *= 1.0 - 0.02 * sin(time * 2.0);
// Slight flicker
color.rgb *= 1.0 + 0.01 * sin(time * 60.0);
return color;
}
SwiftUI ビューモディファイア:
struct CRTEffectModifier: ViewModifier {
@State private var startTime = Date()
var scanlineIntensity: Float = 0.1
var distortionStrength: Float = 0.1
func body(content: Content) -> some View {
TimelineView(.animation) { timeline in
let time = Float(timeline.date.timeIntervalSince(startTime))
GeometryReader { geo in
content
.layerEffect(
ShaderLibrary.crtEffect(
.float(time),
.float2(geo.size),
.float(scanlineIntensity),
.float(distortionStrength)
),
maxSampleOffset: .init(width: 10, height: 10)
)
}
}
}
}
extension View {
func crtEffect(
scanlines: Float = 0.1,
distortion: Float = 0.1
) -> some View {
modifier(CRTEffectModifier(
scanlineIntensity: scanlines,
distortionStrength: distortion
))
}
}
使用方法:
Text("SYSTEM ONLINE")
.font(.system(size: 24, weight: .bold, design: .monospaced))
.foregroundColor(.green)
.crtEffect(scanlines: 0.15, distortion: 0.08)
SwiftUI でのネオングロー
extension View {
func neonGlow(color: Color, radius: CGFloat = 10) -> some View {
self
.shadow(color: color.opacity(0.8), radius: radius / 4)
.shadow(color: color.opacity(0.6), radius: radius / 2)
.shadow(color: color.opacity(0.4), radius: radius)
.shadow(color: color.opacity(0.2), radius: radius * 2)
}
}
// Usage
Text("NEON")
.font(.system(size: 48, design: .monospaced))
.foregroundColor(.cyan)
.neonGlow(color: .cyan, radius: 15)
シェーダーコードの詳細については、metal-shaders-ios.md を参照してください。
プラットフォーム:CSS/Vanilla Web
スキャンラインのオーバーレイ
.crt-container {
position: relative;
background: #000800;
}
.crt-container::after {
content: '';
position: absolute;
inset: 0;
background: repeating-linear-gradient(
0deg,
rgba(0, 0, 0, 0.15),
rgba(0, 0, 0, 0.15) 1px,
transparent 1px,
transparent 2px
);
pointer-events: none;
}
ネオンテキスト効果
.neon-text {
color: #0ff;
text-shadow:
/* White core */
0 0 5px #fff,
0 0 10px #fff,
/* Colored glow layers */
0 0 20px #0ff,
0 0 30px #0ff,
0 0 40px #0ff,
0 0 55px #0ff,
0 0 75px #0ff;
}
CRT の曲面効果(CSS Transform)
.crt-screen {
border-radius: 20px;
transform: perspective(1000px) rotateX(2deg);
box-shadow:
inset 0 0 50px rgba(0, 255, 0, 0.1),
0 0 20px rgba(0, 255, 0, 0.2);
}
CRTFilter.js を使用した WebGL CRT
<script type="module">
import { CRTFilterWebGL } from 'crtfilter';
const canvas = document.getElementById('crt-canvas');
const crt = new CRTFilterWebGL(canvas, {
scanlineIntensity: 0.15,
glowBloom: 0.3,
chromaticAberration: 0.002,
barrelDistortion: 0.1,
staticNoise: 0.03,
flicker: true,
retraceLines: true
});
crt.start();
</script>
効果の手法の詳細については、crt-effects-web.md を参照してください。
効果のパターン
スキャンライン
| プラットフォーム | 実装 |
|---|---|
| CSS | 疑似要素で repeating-linear-gradient を使用 |
| SwiftUI | sin(position.y * frequency) を使った Metal シェーダー |
| WebGL | フラグメントシェーダーによる明るさの調整 |
ブルーム/グロー
| プラットフォーム | 実装 |
|---|---|
| CSS | ぼかしを段階的に強めた複数の text-shadow |
| SwiftUI | 複数の .shadow() モディファイア |
| WebGL | ガウスぼかしパスと加算ブレンド |
| Three.js | luminanceThreshold を指定した UnrealBloomPass |
色収差
| プラットフォーム | 実装 |
|---|---|
| CSS | 色チャンネルをずらして重ねた 3 つの要素 |
| SwiftUI | RGB チャンネルごとに位置をずらしてテクスチャをサンプリング |
| WebGL | チャンネルごとに UV をわずかにずらしてサンプリング |
フリッカー
| プラットフォーム | 実装 |
|---|---|
| CSS | 不透明度を 0.9~1.0 の範囲で変化させる @keyframes アニメーション |
| SwiftUI | タイマー駆動の不透明度変更、またはシェーダーの時間ベース処理 |
| WebGL | 時間ベースのノイズ乗数 |
パフォーマンスに関する考慮事項
CSS 効果
box-shadowとtext-shadowは GPU で高速化されますが、レイヤーが多いと負荷が高くなります- グロー効果のシャドウレイヤーは 4~5 個までにします
- アニメーションする要素には
will-change: transformを使用します prefers-reduced-motionメディアクエリの使用を検討します
SwiftUI/Metal
- Metal シェーダーは、iOS 17 以降に対応するすべてのデバイスで 60~120 fps で動作します
.layerEffect()はすべてのピクセルを処理するため、シェーダーはシンプルに保ちます.compile()を使ってシェーダーを事前コンパイルします(iOS 18 以降)- 古いデバイス(iOS 17 の最小要件は A12)でもテストします
WebGL
- CRTFilter.js はハードウェアアクセラレーションを使用するため、パフォーマンスへの影響は最小限です
- Three.js のブルーム効果では、レンダー・トゥ・テクスチャのパスが必要です
- モバイル向けには、より低解像度のレンダーターゲットを検討します
全般
- モバイルデバイスでは効果の強度を下げます
- アクセシビリティのため、効果を無効にするオプションを用意します
- 空の画面だけでなく、実際のコンテンツを使ってプロファイリングします
テンプレート
すぐに使えるスターターファイル:
- react-tuimorphic-starter.tsx - Tuimorphic を使用した React アプリ
- swiftui-crt-view.swift - CRT エフェクトを使った SwiftUI ビュー
- crt-shader.metal - 完全な Metal シェーダー
- neon-glow.css - CSS によるネオンエフェクト
よくある落とし穴
このセクションでは、TUI の実装を壊す実際の障害モードやエッジケースについて説明します。これらは、複数のプラットフォームで実際に使用する中で見つかった、気づきにくい落とし穴です。
ターミナルの互換性に関する問題
問題: エスケープシーケンスはターミナルエミュレーターによって異なります。iTerm2 で動作するものが、古い xterm、SSH ターミナル、Windows Terminal では動作しないことがあります。
よくある失敗:
- 256 色と ANSI の違い: ターミナルによって対応する色深度が異なります。
#rgbの 16 進カラーを使用するコードは最新のターミナルでは動作しますが、古いシステムでは 16 色の ANSI にフォールバックします。 - リセットシーケンス: テキスト属性(
\x1b[0mまたは\x1b[m)をリセットし忘れると、後続の出力に影響するターミナル状態が残ることがあります。 - Windows での Unicode 枠線描画文字: Windows PowerShell/Terminal では、
─ │ ┌ ┐が正しく表示されないことがあります。リリース前に Windows でテストしてください。 - VT100 の不完全なサポート: 古い Linux システムへの SSH 接続では、VT100 のみのサポートに遭遇することがよくあります。枠線描画文字が文字化けして表示されます。
予防策:
- 対象のターミナルでテストします:iTerm2、Terminal.app、Windows Terminal、SSH Linux、Alacritty
- 枠線描画に失敗する場合は ASCII にフォールバックします:代わりに
+、-、|を使用します - スタイル付きテキストの後は必ず属性をリセットします:
\x1b[0mが役立ちます - ターミナルの機能を検出します:
TERM環境変数(TERM=xterm-256colorとTERM=xterm)を確認します
安全なラッパーの例:
const supportsUnicode = process.env.TERM && process.env.TERM.includes('256');
const border = supportsUnicode ? '─' : '-';
リサイズ時のレイアウト崩れ
問題: ターミナルのサイズを変更すると、固定幅のレイアウトが崩れます。アスペクト比が予期せず変化する、Web ベースの TUI でよく見られます。
よくある失敗:
- 行幅のハードコード: 80 列幅を前提とした中央揃えのヘッダーは、60 列のターミナルでは崩れます。
- 切り詰めずにオーバーフローを非表示: 幅 100% にパディングを加えたコンテンツはコンテナーからはみ出し、テキストが予期せずあふれたり折り返されたりします。
- アスペクト比の前提: スキャンラインのオーバーレイや CRT の曲面は 16:9 を前提としています。ウルトラワイドやモバイルでは、表示上の問題が目立ちます。
- 固定アイテムを使ったグリッドレイアウト:
grid-template-columns: 100px 200px 100pxを指定したdisplay: gridは、小さな画面に対応できません。
予防策:
- ビューポート相対単位を使用します:
vw、vh、またはcalc(100% - Xpx) - 省略記号を使ってテキストを切り詰めます:
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - 次のサイズでテストします:120 列、80 列、40 列(モバイル)、ウルトラワイド
- スキャンラインエフェクトには、ビューポートに合わせて拡大縮小するパーセンテージベースの周波数を使用します
- CSS の
@mediaまたは JS のResizeObserverを使用して、レイアウトを動的に調整します
失敗するスキャンラインの例:
/* ❌ Breaks on narrow screens */
background: repeating-linear-gradient(0deg, transparent, transparent 20px, black 20px, black 21px);
より良い方法:
/* ✅ Scales with viewport height */
background: repeating-linear-gradient(0deg, transparent, transparent calc(2% of height), black calc(2% of height), black calc(2% of height + 1px));
入力処理におけるエッジケース
問題: ユーザー入力は英数字だけではありません。実際の入力には、マルチバイト UTF-8、特殊キー、貼り付けバッファー、オートコンプリートが含まれます。
よくある失敗:
- UTF-8 文字のカウント: JavaScript の
string.lengthは文字数ではなく UTF-16 コード単位をカウントします。4 バイトの絵文字🎮のlength: 2となり、カーソル位置の調整や検証が正しく機能しません。 - 貼り付けバッファーの処理: ユーザーは大量のテキストを貼り付けます。ストリーミングに対応していない入力ハンドラーを使うと、UI がフリーズします。
- 矢印キーと WASD: ターミナルベースのゲームは WASD を前提としますが、ユーザーは矢印キーを期待します。未対応だとユーザー体験が損なわれます。
- 特殊キーの扱いの違い: Ctrl+C、Ctrl+Z、Tab の動作はプラットフォームによって異なります。SSH ターミナルでは一部のメタキーが失われます。
- IME の変換中: 入力メソッドエンジン(中国語、日本語、韓国語用)は、変換が確定する前に未確定文字を送信します。キー入力のたびに検証すると、IME による入力が正しく機能しません。
防止策:
- 書記素クラスタに対応したライブラリを使用します。最新の環境では
Intl.Segmenter、または npm パッケージのgraphemesplitterを使用します。 - 貼り付けは単一のイベントではなく、複数文字のストリームとして処理します。
- ナビゲーションでは矢印キーと WASD の両方をサポートします。
- IME に対応します。
compositionupdateではなく、最終的なcompositionendのときだけ検証します。 - macOS(Command キー)、Windows(Alt)、Linux(さまざまな WM の動作)でテストします。
正しい UTF-8 カーソル位置の調整:
// ❌ Wrong: counts UTF-16 code units
const cursorPos = input.value.length; // "🎮🎮" = length: 4
// ✅ Correct: counts grapheme clusters
const segmenter = new Intl.Segmenter();
const cursorPos = [...segmenter.segment(input.value)].length; // "🎮🎮" = 2
パフォーマンスの問題
問題: TUI のエフェクト(走査線、グロー、ちらつき)は負荷が高くなります。単純な実装は、アニメーションのカクつきや CPU・バッテリーの消耗を引き起こします。
よくある失敗:
- 継続的な再描画: GPU ではなく JS を使って毎フレーム DOM を更新すると、1 秒あたり 60 回以上のリフローが発生します。その結果、アニメーションがカクつき、CPU 使用率が 100%を超えることがあります。
- text-shadow レイヤーの使いすぎ:
text-shadow: 0 0 5px, 0 0 10px, 0 0 20px, ...は、それぞれに個別の GPU パスが必要です。10 個以上のシャドウを使うと、測定可能な遅延が発生します。 - 高頻度のちらつき: ちらつきに
setInterval(..., 16ms)を使うと、アニメーションフレームの時間に同期するため、メインスレッドがビジーのときにカクつきます。 - 走査線オーバーレイの再計算: デバウンスなしでリサイズのたびに
repeating-linear-gradientを再計算すると、アニメーションがカクつきます。 - スロットリングなしの WebGL: ローエンドのモバイル端末で全画面 CRT シェーダーを 120fps で実行すると、すぐにバッテリーを消耗します。
防止策:
- 継続的なエフェクトには CSS の
@keyframesを使用します(GPU によるアクセラレーションが有効です)。 - ネオングローは 3~4 個の text-shadow レイヤーに制限し、モバイル端末でテストします。
- リサイズハンドラーをデバウンスします。
ResizeObserverとスロットリング、またはwindow.requestAnimationFrameを使用します。 - アニメーションする要素で
will-change: transform, filterを控えめに使用します。 - モーションを軽減するオプションとして
prefers-reduced-motion: reduceを提供します。 - モバイルでは CPU 性能を検出し、低電力デバイスではエフェクトをスロットリングします。
パフォーマンスのよいちらつき:
/* ✅ GPU-accelerated, smooth */
@keyframes flicker {
0%, 100% { opacity: 1; }
50% { opacity: 0.95; }
}
.flicker { animation: flicker 3s infinite; }
アニメーションがカクつくちらつき:
// ❌ Main thread blocked, stutter
setInterval(() => {
element.style.opacity = Math.random() > 0.5 ? 1 : 0.9;
}, 50);
アクセシビリティの不足
問題: ターミナル UI では、アクセシビリティが見落とされがちです。スクリーンリーダー、キーボード操作、色のコントラストに問題が生じます。
よくある問題:
- キーボード操作に非対応: クリック可能な要素がマウスにしか反応しません。スクリーンリーダーのユーザーやキーボードだけで操作するユーザーは利用できません。
- 色だけのステータス表示: エラーは赤字、成功は緑字で表示します。色覚異常のあるユーザー(男性の8%、女性の0.5%)には区別できません。
- ARIA ラベルの不足: スクリーンリーダーが「ボタン」とだけ読み上げ、「コマンドを実行」とは伝えません。視覚障害のあるユーザーには分かりにくくなります。
- 固定サイズのテキスト: 48px のネオングローは見栄えがよくても、視覚障害のあるユーザー向けに14pxへ変更すると表示が崩れます。ズームに対応していないと、アクセシビリティが損なわれます。
- フォーカスインジケーターがない: Tab キーでボタンに移動しても、フォーカスリングが表示されません。ユーザーは現在位置を見失います。
- 一時停止できないアニメーション要素: ちらつきや走査線は、光過敏性てんかんを引き起こすことがあります。
prefers-reduced-motionに対応していないと、法的・安全上のリスクが生じます。
予防策:
- キーボード操作に完全対応します:Tab/Shift+Tab、Enter キーでの実行、リストでの矢印キー操作
- スクリーンリーダーでテストします:VoiceOver(macOS)、NVDA(Windows)、JAWS
- ARIA ラベルを追加します:
aria-label="Start system"、aria-describedby="help-text" - テキストのコントラスト比を4.5:1以上にします(WCAG AA 基準)
prefers-reduced-motionに対応する代替設定を用意します:ちらつき、走査線、アニメーションを無効にします- フォーカスインジケーター:
:focus { outline: 2px solid #0ff; }は必須です
アクセシブルなネオンボタン:
<button
aria-label="Execute diagnostic"
className="neon-button"
onClick={handleClick}
onKeyDown={(e) => e.key === 'Enter' && handleClick()}
>
EXECUTE
</button>
<style>
.neon-button:focus {
outline: 2px solid #0ff;
outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
.neon-button {
text-shadow: none; /* Remove glow */
animation: none; /* Remove flicker */
}
}
</style>
ターミナルエミュレーターの違い
プラットフォーム固有の動作に関するクイックリファレンス:
| エミュレーター | 主な特徴・注意点 | 対処法 |
|---|---|---|
| iTerm2(macOS) | すべての Unicode を正しく表示し、トゥルーカラーに対応 | 特に必要ありません |
| Terminal.app(macOS) | 色数が限られ、古い xterm の動作をします | 256色モードでテストします |
| Alacritty | 非常に高速で、トゥルーカラーに対応しますが、一部の Unicode を表示できない場合があります | iTerm2 で動作すれば、問題なく動作します |
| Windows Terminal | トゥルーカラーに対応し、WSL との連携も良好です | 最新のコードでは特に必要ありません |
| PowerShell | 古いバージョンでは罫線文字が ? と表示されます。代替表示を使用します | $PSVersionTable を確認します |
| SSH/xterm | VT100 のみに対応し、トゥルーカラーやマウスイベントには対応しません | ASCII と ANSI カラーにフォールバックします |
| Tmux | トゥルーカラーにはパススルーモードが必要です:set -g default-terminal "tmux-256color" | tmux.conf を設定します |
| Screen | さらに古いターミナルにも対応するため、凝った効果は避けます | ASCII のみのモードを使用します |
プラットフォーム検出の例:
if [[ "$TERM" == "xterm" ]]; then
# Old terminal: use ASCII
BORDER="+"
COLORS="ANSI"
elif [[ "$TERM" == *"256color"* ]]; then
# Modern terminal: use box-drawing + 256 colors
BORDER="─"
COLORS="256"
else
# Unknown: default to safe
BORDER="+"
COLORS="ANSI"
fi
CRT 効果が逆効果になる場合
問題: 走査線、グロー、歪みはモックアップでは見栄えがよくても、実際には問題を引き起こすことがあります。
よくある問題:
- 走査線で読みやすさが低下する: テキストに細い水平線が重なると、特にモバイルや離れた場所から読む場合に読みづらくなります。弱視のあるユーザーには読めないことがあります。
- グローでテキストが隠れる: 強いネオングロー(10層以上の
text-shadow)はテキストの輪郭をぼかし、判読しにくくします。 - 樽型歪みで配置が崩れる: CRT の曲面は本物らしく見えますが、コンテンツがずれているように見えます。ユーザーは UI が壊れていると思うかもしれません。
- ちらつきで乗り物酔いに似た症状が起きる: 微細なちらつきでも、一部のユーザーに吐き気を引き起こします。光過敏性てんかんも、現実的な安全上の懸念です。
予防策:
- 走査線は控えめにします:不透明度を10~15%にし、見えるようにしつつテキストの邪魔にならないようにします
- グローの強さを制限します:グローの半径が20pxを超えると、テキストが読めなくなります
- 歪みは装飾のみに使用します:重要なレイアウトには使用しません
- 必ず
prefers-reduced-motionによる回避手段を用意します - 実際のユーザーでテストします:「無理なく読めますか?」
リソース
ライブラリ
- Tuimorphic - React ターミナル UI コンポーネント
- Inferno - SwiftUI 向け Metal シェーダー
- CRTFilter.js - WebGL CRT 効果
- @react-three/postprocessing - React Three.js 効果
ドキュメント
インスピレーション
- SRCL / Sacred Computer - Tuimorphic のデザインに影響を与えたサイト
- Cool Retro Term - CRT エフェクトを備えたターミナルエミュレーター