mcpField
T
tui-designer

tui-designer

github.com/ckorhonenデザイン・UI2293 回インストール5 週間前に更新
リポジトリ

tui-designer について

React、SwiftUI、CSS、WebGLでレトロやサイバーパンク調のテキストUIを設計するスキルです。CRT効果、ネオン表現、配色、タイポグラフィ、実装上の注意点を扱います。

主な機能

  1. 01CRTとスキャンライン効果
  2. 02ネオン発光のスタイル
  3. 03レトロな配色とタイポグラフィ
  4. 04React Tuimorphicコンポーネントの指針
  5. 05SwiftUI MetalとWebの手法
  6. 06クロスプラットフォームの注意点と性能

活用シーン

  1. 01ターミナル風インターフェースの設計
  2. 02SwiftUIビューへのCRT効果追加
  3. 03ネオン調のReactまたはWeb UI構築
  4. 04配色と等幅フォントの選定

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/ckorhonen/claude-skills --skill tui-designer

インストール後、Claude Code で/tui-designerを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
color-palettes.md
copywriting-guide.md
crt-effects-web.md
design-patterns.md
metal-shaders-ios.md
tuimorphic-reference.md
typography-guide.md
scripts/
generate_palette.py
templates/
crt-shader.metal
neon-glow.css
react-tuimorphic-starter.tsx
swiftui-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サブのアクセント
エレクトリックブルー#0066ff3 番目の色
ホットピンク#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 を参照してください。

コピーライティング

端末インターフェースには、簡潔で技術的、かつ権威ある独特の語り口があります。プロジェクトで指定されていない限り、以下を標準として使ってください。

基本原則
  1. 簡潔かつ明快に - すべての言葉に意味を持たせる
  2. 技術的な権威性 - システムは自分の役割を理解している
  3. 機械的な正確さ - 曖昧な表現、謝罪、埋め草を使わない
テキストの書式
要素表記例
ヘッダー/タイトルすべて大文字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

主なコンポーネント
コンポーネント用途
Buttonvariant="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 を使用
SwiftUIsin(position.y * frequency) を使った Metal シェーダー
WebGLフラグメントシェーダーによる明るさの調整
ブルーム/グロー
プラットフォーム実装
CSSぼかしを段階的に強めた複数の text-shadow
SwiftUI複数の .shadow() モディファイア
WebGLガウスぼかしパスと加算ブレンド
Three.jsluminanceThreshold を指定した UnrealBloomPass
色収差
プラットフォーム実装
CSS色チャンネルをずらして重ねた 3 つの要素
SwiftUIRGB チャンネルごとに位置をずらしてテクスチャをサンプリング
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 のブルーム効果では、レンダー・トゥ・テクスチャのパスが必要です
  • モバイル向けには、より低解像度のレンダーターゲットを検討します
全般
  • モバイルデバイスでは効果の強度を下げます
  • アクセシビリティのため、効果を無効にするオプションを用意します
  • 空の画面だけでなく、実際のコンテンツを使ってプロファイリングします

テンプレート

すぐに使えるスターターファイル:


よくある落とし穴

このセクションでは、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/xtermVT100 のみに対応し、トゥルーカラーやマウスイベントには対応しません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 による回避手段を用意します
  • 実際のユーザーでテストします:「無理なく読めますか?」

リソース

ライブラリ
ドキュメント
インスピレーション

コメント