UI/UX

目次

概要

使えるだけでなく、迷わず使えるようにする

UIは利用者が触れる面であり、UXは利用者が目的を達成するまでの体験全体です。優れたUI/UXは見た目だけでは決まりません。利用者の文脈、情報の整理、フィードバック、エラー時の回復、アクセシビリティ、性能まで含めて設計します。

要点

UI/UXは「きれいな画面を作ること」ではなく、利用者が目的を達成するまでの摩擦を減らす設計です。

この章で重視すること

  • UIとUXを分けて考える
  • 画面単体ではなく利用者の流れを見る
  • 正常系だけでなくエラー、待ち時間、空状態を設計する
  • アクセシビリティを後付けにしない

UIとUXの違い

用語 意味
UI 操作する接点 ボタン、フォーム、メニュー、画面
UX 目的達成までの体験 探す、理解する、入力する、完了する

見た目が整っていても、目的の操作にたどり着けなければUXは悪くなります。逆に、UIが地味でも、迷わず速く完了できるならよい体験になり得ます。

利用者の文脈

利用者は、静かな机で余裕を持って操作するとは限りません。

  • スマートフォンで片手操作している
  • 急いでいる
  • 専門用語に慣れていない
  • 通信が遅い
  • 画面読み上げを使っている
  • 失敗すると業務やお金に影響する

UI/UXは、理想的な利用者ではなく、現実の文脈に合わせて設計します。

情報設計

情報設計は、何をどの順番で見せるかを決めることです。

flowchart TD Goal["利用者の目的"] Info["必要な情報"] Group["分類と優先順位"] Screen["画面構成"] Action["行動"] Goal --> Info --> Group --> Screen --> Action

情報量が多い場合、すべてを同じ強さで見せると何も目立たなくなります。

優先順位の基本

  • まず判断に必要な情報を出す
  • 詳細は折りたたむ、または別画面へ逃がす
  • 危険な操作は確認できる位置に置く
  • 主要アクションを一つに絞る

インタラクション設計

インタラクション設計では、操作した結果が分かることが重要です。

操作 必要なフィードバック
ボタンを押す 押されたことが分かる
保存する 処理中、成功、失敗が分かる
入力する 入力形式や不足が分かる
削除する 取り消せるか、確認がある

利用者はシステム内部を見られません。状態を適切に返すことで、安心して操作できます。

ユーザビリティヒューリスティック

Nielsen Norman Groupの10 Usability Heuristicsは、UIをレビューするときの古典的な判断軸です。特に次の観点は、多くの画面でそのまま使えます。

観点 UIで見ること
システム状態の可視化 処理中、成功、失敗が分かるか
現実世界との一致 利用者の言葉で表現しているか
ユーザーの制御 戻る、取り消す、やり直すができるか
一貫性 同じ操作が同じ意味になっているか
エラー予防 失敗しにくい入力になっているか
記憶より認識 選択肢や状態が見えているか

アクセシビリティ

アクセシビリティは特別対応ではなく、品質の一部です。

  • キーボードだけで操作できる
  • フォーカス位置が見える
  • 色だけに意味を持たせない
  • 画像に代替テキストを付ける
  • 見出し構造を正しく使う
  • フォームラベルを明示する
  • コントラストを確保する

アクセシビリティを意識すると、スクリーンリーダー利用者だけでなく、疲れている人、屋外で画面を見る人、慣れていない人にも使いやすくなります。

W3CのWCAG 2.2は、キーボード操作、フォーカス表示、入力支援、エラー識別、認証のしやすさなどをテスト可能な基準として整理しています。UIを作るときは、見た目のレビューだけでなく、キーボード、スクリーンリーダー、ズーム、色覚差の観点でも確認します。

フォーム設計

フォームは失敗しやすいUIです。入力欄を並べるだけでは不十分です。

よいフォームの条件

  • 必須項目が分かる
  • 入力例がある
  • エラーが該当項目の近くに出る
  • 送信前に不備を直せる
  • 入力済み内容が失われない
  • モバイルで適切なキーボードが出る
  • プレースホルダーだけに説明を任せない
  • 入力エラーをプログラムからも関連付ける
悪い例:
  エラーがあります

よい例:
  メールアドレスの形式が正しくありません。
  例: user@example.com

状態設計

UIには多くの状態があります。

  • 初期状態
  • 読み込み中
  • 成功
  • 空状態
  • 入力中
  • バリデーションエラー
  • 通信エラー
  • 権限なし
  • 部分的な失敗

正常系だけで画面を作ると、実運用で体験が崩れます。

エラーと空状態

エラー表示では、何が起きたか、利用者が次に何をすればよいかを伝えます。

状態 悪い例 よい例
通信失敗 エラー 接続できません。時間をおいて再試行してください。
権限なし Forbidden この操作には管理者権限が必要です。
空状態 データがありません まだ請求書がありません。最初の請求書を作成できます。

空状態は、次の行動を案内できる重要な場所です。

デザインシステム

デザインシステムは、UIの部品集だけではありません。

  • 色、余白、タイポグラフィ
  • コンポーネント
  • 利用ルール
  • アクセシビリティ基準
  • 文言のトーン
  • 実装上のAPI

個別画面の品質を上げるだけでなく、チーム全体の一貫性を保つための仕組みです。

計測と改善

UXは主観だけでは評価できません。定量と定性を組み合わせます。

方法 見えること
アクセス解析 どこで離脱しているか
ファネル分析 目的達成までの詰まり
ユーザーテスト なぜ迷うか
問い合わせ分析 何が理解されていないか
Core Web Vitals 表示や応答の体感品質

GoogleのHEART Frameworkは、Happiness、Engagement、Adoption、Retention、Task Successの5軸でUX指標を整理します。すべてを測る必要はありませんが、機能ごとに「ゴール、シグナル、メトリクス」を分けると、見た目の好みではなく利用者の成果で改善を判断できます。

Goal:
  初回ユーザーが迷わず請求書を作成できる

Signal:
  作成途中で離脱しない
  エラーを自力で解決できる

Metric:
  初回作成完了率
  入力エラー後の回復率
  完了までの時間

実装との接続

UI/UXはフロントエンドだけの問題ではありません。

  • APIレスポンスが遅いと体験が悪くなる
  • エラーコードが曖昧だと適切な案内ができない
  • 権限設計が粗いと画面制御が複雑になる
  • ログがないと改善点を測れない

そのため、UI/UXはAPI設計、システム設計、オブザーバビリティともつながります。

UIレビューの流れ

UIレビューでは、好みの議論から始めると収束しにくくなります。Nielsen Norman Groupのヒューリスティック、web.devのアクセシビリティ教材、WCAGの基準を合わせて使うと、見た目、操作、支援技術、性能を同じレビューの中で扱えます。

flowchart LR Goal["利用者の目的"] Flow["主要フロー"] State["状態設計"] A11y["アクセシビリティ"] Copy["文言"] Metrics["計測"] Backlog["改善バックログ"] Goal --> Flow --> State --> A11y --> Copy --> Metrics --> Backlog
レビュー観点 確認すること よくある失敗
目的 利用者が何を完了したいかが明確か 画面単位で考え、利用者の流れを見ていない
状態 初期、読み込み中、成功、失敗、空状態があるか 正常系だけをデザインしている
フィードバック 操作結果がすぐ分かるか 保存中なのか失敗したのか分からない
アクセシビリティ キーボード、フォーカス、ラベル、コントラストを確認したか 見た目だけでレビューしている
エラー回復 原因と次の行動が伝わるか 「エラーです」だけで止まる
計測 改善後に良くなったか判断できるか リリースして終わりになる

フォームや管理画面では、入力前入力中送信中失敗後完了後 の5状態を並べて確認すると抜け漏れが見つかりやすくなります。特にラベル、エラーメッセージ、フォーカス移動、入力済みデータの保持は、見た目の完成度より利用者の成功率に直結します。

要点

UIレビューは「美しいか」だけではなく、「目的を達成できるか」「失敗しても戻れるか」「誰でも操作できるか」「改善を測れるか」を順に見ると安定します。

情報設計の粒度

UI設計では、情報をどの単位で見せるかが重要です。利用者はデータベースのテーブル構造ではなく、自分の目的に沿って情報を探します。

悪い粒度 よい粒度
DB項目をそのまま並べる 判断に必要な情報をまとめる
すべて同じ強さで表示する 重要度で階層をつける
画面ごとに用語が違う 同じ概念は同じ名前にする
操作と結果が離れている 操作の近くに結果を返す

情報設計では、利用者が次に何を判断するかを先に置きます。

フォームのアクセシビリティ

入力欄には、視覚的にもプログラム的にもlabelが必要です。placeholderだけでは、入力後に意味が消えたり、支援技術へ意図が伝わりにくくなります。

確認すること:

  • labelが常に見えるか
  • 必須条件が文字で説明されているか
  • error messageが該当項目と関連付いているか
  • keyboardだけで送信まで進めるか
  • focus indicatorが見えるか
  • autocompleteやinput typeが適切か

フォームはUIの中でも離脱と失敗が起きやすいので、正常系よりエラー回復を丁寧に設計します。

マイクロコピー

短い文言は、UIの使いやすさに大きく影響します。

場面 悪い例 よい例
削除確認 本当にいいですか 請求書を削除します。この操作は戻せません。
入力エラー 不正です 郵便番号は7桁の数字で入力してください。
空状態 ありません まだプロジェクトがありません。最初のプロジェクトを作成できます。
待機中 処理中 ファイルをアップロードしています。完了まで数分かかることがあります。

文言は飾りではなく、システム状態と次の行動を伝えるインターフェースです。

ヒューリスティック評価

UI/UXの改善では、利用者テストが理想ですが、毎回十分な人数を集められるとは限りません。その場合、Nielsen Norman Groupの10 usability heuristicsのような観点を使うと、初期段階でも問題を見つけやすくなります。

特に実装レビューで使いやすい観点は次です。

観点 確認すること
状態の可視性 保存中、失敗、完了が分かるか
現実世界との一致 利用者の言葉で説明しているか
操作の自由 undo、cancel、戻る導線があるか
一貫性 同じ操作が同じ見た目と挙動か
エラー予防 送信前に危険な操作を止められるか
再認より記憶 ユーザーに覚えさせていないか

Apple HIGのようなプラットフォームガイドラインは、見た目の統一だけでなく、利用者がすでに知っている操作期待に合わせるためにも役立ちます。

デザインシステムとコンポーネントライブラリ

デザインシステムの体系

Google Material Design、Apple HIG、Figmaのコンポーネントライブラリなど、大規模プロダクトはシステム化した UI を採用。

デザインシステムの構成要素:

Design Tokens:
  - Colors: primary (#1F77D2), secondary (#F09A1D)
  - Typography: Heading1 (32px, bold), Body (16px, regular)
  - Spacing: 8px, 16px, 24px, 32px(8pxグリッド基準)
  - Shadows: elevation-1, elevation-2(深度表現)
  - Breakpoints: 320px (mobile), 768px (tablet), 1200px (desktop)

Component Library:
  - Button (primary, secondary, disabled states)
  - Input Fields (text, email, password)
  - Modals & Dialogs
  - Navigation (tabs, breadcrumb, sidebar)
  - Cards & Lists

Documentation:
  - Usage guidelines
  - Code snippets (React, Vue, etc.)
  - Design specs & rationale
  - Accessibility requirements

実装例(CSS-in-JS による系統化):

// design-tokens.js
export const tokens = {
  colors: {
    primary: '#1F77D2',
    secondary: '#F09A1D',
    danger: '#D14747',
    neutral: {
      50: '#F9FAFB',
      900: '#111827'
    }
  },
  typography: {
    heading1: {
      fontSize: '32px',
      fontWeight: 700,
      lineHeight: '1.2'
    },
    body: {
      fontSize: '16px',
      fontWeight: 400,
      lineHeight: '1.5'
    }
  },
  spacing: [0, 4, 8, 16, 24, 32, 48, 64]
};

// Button.jsx
import styled from 'styled-components';
import { tokens } from './design-tokens';

export const Button = styled.button`
  padding: ${tokens.spacing[2]}px ${tokens.spacing[3]}px;
  background-color: ${props => props.variant === 'primary' ? tokens.colors.primary : tokens.colors.secondary};
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: ${tokens.typography.body.fontSize};
  
  &:hover {
    opacity: 0.9;
  }
  
  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
`;

アクセシビリティ (A11y)

WCAG 2.1 ガイドライン

3段階の適合レベル:A(最小)→ AA(推奨)→ AAA(拡張)

主要要件:

項目 AA レベル 実装
色コントラスト 4.5:1 以上 テキスト vs 背景
フォントサイズ 最小 12px 読みやすさ
キーボードナビゲーション すべての機能が Tab キーで操作可能 tabindex の管理
スクリーンリーダー対応 ARIA ラベル使用 aria-label, role 属性
動画字幕 同期した字幕提供 VTT ファイル

実装例(React + ARIA):

// コントラストチェック
const contrastRatio = (foreground, background) => {
  const getLuminance = (hex) => {
    const rgb = parseInt(hex.slice(1), 16);
    const [r, g, b] = [(rgb >> 16) & 255, (rgb >> 8) & 255, rgb & 255];
    const [rs, gs, bs] = [r, g, b].map(x => x / 255 <= 0.03928 ? x / 3058 : Math.pow((x / 255 + 0.055) / 1.055, 2.4));
    return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
  };
  
  const l1 = getLuminance(foreground);
  const l2 = getLuminance(background);
  const lighter = Math.max(l1, l2);
  const darker = Math.min(l1, l2);
  
  return (lighter + 0.05) / (darker + 0.05);
};

// スクリーンリーダー対応
export function AccessibleButton({ children, ariaLabel, ...props }) {
  return (
    <button
      aria-label={ariaLabel || children}
      role="button"
      aria-pressed={props.pressed}
      {...props}
    >
      {children}
    </button>
  );
}

// フォーカス管理
export function Modal({ isOpen, onClose }) {
  const focusTrapRef = useRef(null);
  
  useEffect(() => {
    if (isOpen) {
      focusTrapRef.current?.focus();
      
      const handleKeyDown = (e) => {
        if (e.key === 'Escape') onClose();
      };
      document.addEventListener('keydown', handleKeyDown);
      return () => document.removeEventListener('keydown', handleKeyDown);
    }
  }, [isOpen]);
  
  return (
    <dialog open={isOpen} ref={focusTrapRef}>
      {/* Modal content */}
    </dialog>
  );
}

パフォーマンス最適化

Core Web Vitals

Google が定義した 3 つの重要メトリクス:

メトリクス 目標値 測定
LCP (Largest Contentful Paint) < 2.5 秒 最大の要素がレンダリングされる時間
FID (First Input Delay) < 100 ms ユーザー入力への応答時間
CLS (Cumulative Layout Shift) < 0.1 ページの予期しない layout ズレ

最適化戦略:

// LCP 最適化:画像の遅延ロード
<img
  src="image.jpg"
  loading="lazy"  // 画面外の画像は遅延ロード
  decoding="async"  // メインスレッドをブロックしない
/>

// FID 最適化:Long Task の分割
function processLongTask(data) {
  const chunkSize = 100;
  
  function processChunk(startIndex) {
    const endIndex = Math.min(startIndex + chunkSize, data.length);
    
    // チャンク処理
    for (let i = startIndex; i < endIndex; i++) {
      // 処理
    }
    
    if (endIndex < data.length) {
      // 次のチャンクをスケジュール
      // メインスレッドが空くまで待機
      setTimeout(() => processChunk(endIndex), 0);
    }
  }
  
  processChunk(0);
}

// CLS 最適化:レイアウト変動の予防
.container {
  contain: layout;  /* 子要素の layout が親に影響しない */
}

// 画像サイズを明示
<img src="..." width="800" height="600" />

// フォント読み込み最適化
@font-face {
  font-family: 'CustomFont';
  src: url('font.woff2') format('woff2');
  font-display: swap;  /* フォール フォントで先に表示 */
}

バンドルサイズ削減

// 動的インポート(コード分割)
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));

export function Page() {
  return (
    <Suspense fallback={<Loading />}>
      <HeavyComponent />
    </Suspense>
  );
}

// Tree-shaking(使われないコードの削除)
// package.json
{
  "sideEffects": false,  // すべてのモジュールは side-effect なし
  "exports": {
    ".": "./dist/index.js",
    "./utils": "./dist/utils.js"  // 粒度の細かい import
  }
}

// import したもののみバンドルに含まれる
import { usefulFunction } from 'library';  // OK
import 'library';  // 警告:何も使わない

// Tree-shaking の確認(Webpack Bundle Analyzer)
// npm install webpack-bundle-analyzer

ユーザーリサーチとテスト

A/B テスト設計

class ABTestEngine {
  constructor() {
    this.variants = new Map();
    this.userAssignments = new Map();
  }
  
  assignVariant(userId, experimentId) {
    const key = `${experimentId}-${userId}`;
    
    if (!this.userAssignments.has(key)) {
      // 一貫性のためハッシュベースで割り当て
      const hash = this.simpleHash(userId + experimentId);
      const variant = hash % 2 === 0 ? 'control' : 'treatment';
      this.userAssignments.set(key, variant);
    }
    
    return this.userAssignments.get(key);
  }
  
  trackEvent(userId, experimentId, event, value) {
    const variant = this.assignVariant(userId, experimentId);
    
    // 送信
    analytics.log({
      experiment: experimentId,
      variant,
      event,
      value,
      timestamp: Date.now()
    });
  }
  
  simpleHash(str) {
    let hash = 0;
    for (let i = 0; i < str.length; i++) {
      hash = ((hash << 5) - hash) + str.charCodeAt(i);
      hash |= 0;
    }
    return Math.abs(hash);
  }
}

// 使用例
const abTest = new ABTestEngine();
const variant = abTest.assignVariant('user123', 'checkout-button-v2');

if (variant === 'control') {
  // 従来のボタン
  return <Button>チェックアウト</Button>;
} else {
  // 新しいボタン
  return <Button highlight>今すぐ買う</Button>;
}

ユーザテスト(Usability Testing)

// スクロール深度の追跡
class ScrollDepthTracker {
  constructor(pageUrl) {
    this.pageUrl = pageUrl;
    this.trackedScrolls = new Set();
    
    window.addEventListener('scroll', () => this.trackScrollDepth());
  }
  
  trackScrollDepth() {
    const scrollPercent = 
      (window.scrollY + window.innerHeight) / document.documentElement.scrollHeight * 100;
    
    const milestones = [25, 50, 75, 100];
    
    for (const milestone of milestones) {
      if (scrollPercent >= milestone && !this.trackedScrolls.has(milestone)) {
        this.trackedScrolls.add(milestone);
        analytics.log({
          event: 'scroll_depth',
          page: this.pageUrl,
          depth: milestone,
          timestamp: Date.now()
        });
      }
    }
  }
}

// クリック熱図用データ収集
class ClickHeatmapCollector {
  constructor() {
    document.addEventListener('click', (e) => {
      const rect = e.target.getBoundingClientRect();
      const clickData = {
        x: e.clientX - rect.left,
        y: e.clientY - rect.top,
        element: e.target.tagName,
        elementId: e.target.id,
        timestamp: Date.now()
      };
      
      analytics.log({
        event: 'click_heatmap',
        ...clickData
      });
    });
  }
}

最新のUI/UXトレンド

Dark Mode 実装

/* Light mode (デフォルト) */
:root {
  --bg-primary: #ffffff;
  --text-primary: #000000;
  --text-secondary: #666666;
}

/* Dark mode */
@media (prefers-color-scheme: dark) {
  :root {
    --bg-primary: #1a1a1a;
    --text-primary: #ffffff;
    --text-secondary: #cccccc;
  }
}

/* ユーザーの明示的な選択も尊重 */
[data-theme="dark"] {
  --bg-primary: #1a1a1a;
  --text-primary: #ffffff;
}

[data-theme="light"] {
  --bg-primary: #ffffff;
  --text-primary: #000000;
}

body {
  background-color: var(--bg-primary);
  color: var(--text-primary);
  transition: background-color 0.3s ease;
}

マイクロインタラクション

// ローディングスピナー with animation
const LoadingSpinner = styled.div`
  width: 40px;
  height: 40px;
  border: 4px solid #f3f3f3;
  border-top: 4px solid #3498db;
  border-radius: 50%;
  animation: spin 1s linear infinite;
  
  @keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
  }
`;

// Ripple effect (Material Design)
const RippleButton = styled.button`
  position: relative;
  overflow: hidden;
  
  &::before {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: 0;
    height: 0;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.5);
    transform: translate(-50%, -50%);
    animation: ripple 0.6s ease-out;
  }
  
  @keyframes ripple {
    to {
      width: 300px;
      height: 300px;
      opacity: 0;
    }
  }
`;

// スムーズなスクロール
html {
  scroll-behavior: smooth;
}

// ページロード時のフェード
export function FadeInComponent({ children }) {
  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      transition={{ duration: 0.5 }}
    >
      {children}
    </motion.div>
  );
}

レスポンシブデザイン

/* Mobile First */
.container {
  display: grid;
  grid-template-columns: 1fr;  /* 1列 */
  gap: 1rem;
}

/* タブレット以上 */
@media (min-width: 768px) {
  .container {
    grid-template-columns: 1fr 1fr;  /* 2列 */
  }
}

/* デスクトップ以上 */
@media (min-width: 1200px) {
  .container {
    grid-template-columns: 1fr 1fr 1fr;  /* 3列 */
  }
}

/* 高 DPI ディスプレイ(Retina) */
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
  .icon {
    background-image: url('icon@2x.png');
    background-size: 24px 24px;
  }
}

/* Container Queries(新) */
@container (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 1fr 1fr;
  }
}

Norman の Design of Everyday Things: ユーザーが操作を理解しやすいUIを設計する考え方は、Donald Norman による「Affordance」と「Feedback」の理論に基づいています:

  • Affordance: UI要素がもつ「使用可能性の手がかり」。ボタンは「押せるもの」に見えるべき
  • Feedback: ユーザー操作に対する即座の応答。「送信」ボタン押下直後にローディング表示が出ることで、「システムが命令を受け取った」という確実性をユーザーに提供

Recognition vs Recall: UI設計において、「ユーザーが覚えるべき情報を最小化し、画面に表示された選択肢から認識させる」(Recognition)ことが、「ユーザーがコマンドを記憶して入力する」(Recall)よりも認知負荷が低いとされています。例えば、メニュードリブンなUI設計は Recall の負担を軽減します。

色彩設計とアクセシビリティ

WCAG色対比基準: アクセシブルな配色を設計するときは、以下の定量的基準を確認します:

  • レベルAA: 通常テキストは 4.5:1、大型テキストは 3:1 以上の色対比
  • レベルAAA: 通常テキストは 7:1、大型テキスト は 4.5:1 以上

色覚障害への対応: 色覚異常のユーザー(赤色盲、緑色盲、青-黄色盲)でも識別できる配色設計が必要です。Google Material Design では、複数の色を組み合わせることで、色彩情報を冗長化する推奨が記載されています。

タイポグラフィと読みやすさ

行長と読みやすさ: 読みやすさは、以下の指標である程度定量化できます:

  • 最適行長: 50~75 文字(英語)、30~40 文字(日本語)とされており、文字サイズとウィンドウ幅の関係で実現
  • 行送り(Line Height): 1.5 倍程度(1.5em)が読みやすいとされており、タイプフェイスの種類により調整

フォント選定戦略: システムフォント(San Francisco, Roboto等)の採用により、プラットフォーム固有の最適なレンダリングが保証され、ダウンロードオーバーヘッドも回避できます。

情報設計とナビゲーション

階層とメンタルモデル: ナビゲーション設計では、ユーザーが「このアプリケーション全体をどう理解しているか」を支える必要があります。深すぎる階層(3段階以上)は「迷路」となり、フラットすぎる階層(1段階)は「情報爆発」になります。

Breadcrumb Navigation: パンくずナビゲーション(「Home > Products > Electronics」)は、ユーザーが「今、どのレベルのカテゴリーにいるのか」を常に認識させる効果的な手法です。

レスポンシブ設計とデバイス対応

ブレークポイント設定: レスポンシブデザインでは、以下のような画面幅を目安にレイアウトを切り替えます:

  • モバイル: < 600px
  • タブレット: 600px ~ 1024px
  • デスクトップ: > 1024px

モバイルファースト: 制約の厳しい環境(モバイル)から設計を始め、デスクトップで機能を追加することで、「本質的に必要な機能」が浮き彫りになり、設計品質が向上します。

パフォーマンス指標

ユーザー体感指標: Google が定義した Core Web Vitals は、以下の3つの指標で測定されます:

  • LCP (Largest Contentful Paint): 最大の視覚要素がレンダリングされるまでの時間(目標: 2.5秒以内)
  • FID (First Input Delay): ユーザーが最初にインタラクションしてからレスポンスが返るまでの時間(目標: 100ms以内)
  • CLS (Cumulative Layout Shift): レイアウト変動の総計(目標: 0.1以下)

測定と最適化: これらのメトリクスは Lighthouse、PageSpeed Insights、Web Vitals JavaScript ライブラリで測定でき、改善施策の効果を定量的に評価できます。

アニメーションとモーション設計

意図的な動き: アニメーションは、単なる「派手さ」ではなく、以下の機能を果たします:

  • フィードバック: ユーザー操作への応答(ボタン押下時の色変化)
  • 遷移: 画面遷移時の連続性(Shared Element Transition)
  • ガイダンス: ユーザー注目を引く(重要な要素のパルス効果)

パフォーマンス: アニメーション実装時は CSS トランジション/アニメーション、transform プロパティを優先し、ペイント処理を避けることで 60fps を実現します。

Nielsen の 10 個のユーザビリティ原則

Nielsen Norman Group は 30 年以上のアイアイトラッキングとユーザテストから、ユーザビリティの 10 の原則を導き出しました。これはウェブからアプリまで通用する普遍的な設計指針です。

1. システムの状態の可視性

ユーザは常に「今どこにいるのか」「何が起きているのか」を知るべき。

例:
- NG: フォーム送信で何も表示されない
- OK: 「送信中...」→ 「送信完了」
- OK: 進捗バーで処理の進みを表示

実装: Progress indicator, Loading state, Breadcrumb, Page title

2. システムとユーザの一致

システムの用語は実世界とユーザのメンタルモデルに合わせるべき。

例:
- NG: 医学用語「myocardial infarction」
- OK: 日常用語「heart attack」
- NG: 「キャッシュをクリア」→ ユーザは何が消えるか不明
- OK: 「保存済みパスワードを削除」

3. ユーザ制御と自由

システムは緊急の出口を提供し、ユーザが誤った操作から抜け出せるようにすべき。

実装:
- Undo / Redo
- Cancel / Back ボタン
- Confirmation dialogs for destructive actions
  (削除、フォーマット、キャンセル不可操作)

4. システムの安全

重要な操作は取り返しのつかない結果を避けるべき。

例:
- NG: 「削除」ボタンを赤くするだけ
- OK: 削除確認ダイアログ + ユーザが「削除」と入力 + Undo 機能
- OK: 「アーカイブ」オプション(削除よりマイルド)

5. エラー予防と対応

エラーを起こさせないデザインが最善。次に、ユーザがエラーから素早く回復できるべき。

予防例:
- Constraint validation(フォーム送信前に即座に)
- Disable invalid actions(合計が予算を超えたら購入ボタンを disabled)
- Confirmation for risky actions

回復例:
- エラーメッセージは具体的に:
  - NG: "Invalid input"
  - OK: "Email address must contain @ symbol"
- エラー位置を明確に:フォーム内の問題フィールドをハイライト

6. 認識より想起

ユーザが「覚える」負荷を減らすべき。選択肢は見える形で提供。

例:
- NG: ユーザが記憶している命令(`/help`, `--verbose`- OK: メニューに「ヘルプ」「詳細表示」が見える
- NG: 日付フォーマットのルールを覚えさせる
- OK: Date picker

7. 効率性

操作の最短経路をサポートするべき。ただし初心者用と上級者用を分ける。

例:
- キーボードショートカット(上級者向け)
- メニュー(初心者向け)
- 最近使ったアイテム(効率性)

8. 美的でミニマルなデザイン

関係のない情報を避けるべき。UI は最も重要な要素に焦点を当てる。

例:
- NG: ページに 50 個のリンクが表示されている
- OK: 主要 5-7 個のアクション、その他は「もっと見る」
- NG: Modal に背景デコレーション
- OK: Modal に焦点が当たるように背景を暗くする(フォーカストラッピング)

9. エラーメッセージは平易な言語で

ユーザが理解できる平易で親切な言葉:

NG: "HTTP 422: Unprocessable Entity"
OK: "We couldn't process your payment. Your card may be expired."

NG: "SIGKILL received, PID 1234 terminated"
OK: "The app encountered an issue and closed. Please restart."

10. ヘルプとドキュメント

ヘルプは必要な時にアクセス可能で、具体的なタスク指向であるべき。

実装:
- Context-sensitive help(その画面で必要な help)
- ツールチップ
- FAQ
- Live chat support
- In-app tutorials(最初のユーザ向け)

Material Designの実装パターン

Google が推進する Material Design は、3D 空間のメタファー(elevation, shadow)を使い、可読性と操作感を統一。

高さ表現と影

Material はカードのように積み重ねられる概念。z-axis で深さを表現。

/* dp = density-independent pixels
   Base elevation: 0dp
   Raised states: 4dp, 8dp, 16dp, 24dp
*/

.card {
  elevation: 0;  /* base state */
  box-shadow: none;
}

.card:hover {
  elevation: 4;
  box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}

.card:active {
  elevation: 8;
  box-shadow: 0 4px 8px rgba(0,0,0,0.3);
}

.modal {
  elevation: 24;  /* 最も高い elevation、背景を完全に覆う */
  box-shadow: 0 12px 24px rgba(0,0,0,0.4);
}

Color System

Material は primary、secondary、error color を中心とした色体系を定義:

Primary color:     主要アクション(CTA、selection)
Secondary color:   補足アクション(フィルタ、情報表示)
Error color:       警告、バリデーション失敗

メリット:

  • 色を少数に絞ることで視覚的な統一性
  • アクセシビリティ:コントラスト比チェック済み
  • ダークモード対応が容易(色パレットの反転)

Typography(タイポグラフィ)

Material では タイプスケール(Typescale)として、異なるサイズの組み合わせ:

Display Large:    57 / 64 (headline, page title)
Display Medium:   45 / 52
Display Small:    36 / 44

Headline Large:   32 / 40 (section title)
Headline Medium:  28 / 36
Headline Small:   24 / 32

Title Large:      22 / 28 (card title, subheading)
Title Medium:     16 / 24
Title Small:      14 / 20

Body Large:       16 / 24 (regular text)
Body Medium:      14 / 20
Body Small:       12 / 16

Label Large:      14 / 20 (button text, chip)
Label Medium:     12 / 16
Label Small:      11 / 16

見出しのサイズは視覚的階層を作り、ユーザが情報構造を素早く理解できます。

エラーパターン

Material Design のエラー表示は 4 段階:

  1. Validation Errors(入力時)
    • フィールドの下に赤いテキスト
    • フィールド枠を error color で囲む
    • ユーザが修正途中に自動に消える
<input type="email" />
<span class="error-text">Valid email required (e.g., user@example.com)</span>
  1. Helper Text(事前案内)

    • フォーム補助テキストで入力ルールを事前案内
    • 入力時に validation message に切り替わる
  2. Confirmation Dialogs(危険操作)

    • 削除やキャンセル前に確認ダイアログ
    • Primary と Secondary ボタンを分ける
  3. Toast / Snackbar(操作後のフィードバック)

    • 下部に一時的に表示
    • 「送信しました」「エラーが発生しました」の通知

Apple HIGの設計思想

Apple Human Interface Guidelines は、iOS / macOS で一貫した使い心地を提供。

主要な原則

  • Clarity(明確性):テキストが読みやすく、アイコンが明白
  • Consistency(一貫性):ジェスチャー、用語、視覚スタイルが統一
  • Feedback(フィードバック):タップ後に即座に視覚的/触覚的反応
  • Aesthetics(美学):不必要な情報を排除

例:iOS での Bottom Navigation

タブバーは 5 個以下の主要ナビゲーション:

| Home | Search | Add | Favorite | Profile |

- 各タブはシンプルなアイコン
- 選択中タブは filled、非選択は outline
- タップ時に haptic feedback(バイブ)
- ラベルは必須(アイコンのみでなく)

Form Design:入力体験の最適化

複雑なフォームほど設計が重要。Nielsen / Material Design / Apple の知見をまとめた実装パターン:

1カラムレイアウト

<!-- NG:複数列 -->
<form>
  <div class="grid" style="grid-template-columns: 1fr 1fr;">
    <input placeholder="First Name" />
    <input placeholder="Last Name" />
    <input placeholder="Email" />
    <input placeholder="Phone" />
  </div>
</form>

<!-- OK:単一列 -->
<form>
  <div class="single-column">
    <input placeholder="First Name" />
    <input placeholder="Last Name" />
    <input placeholder="Email" />
    <input placeholder="Phone" />
  </div>
</form>

単一列は行をスキャンしやすく、入力完了率が向上。

入力欄の状態管理

// 入力フィールドの 3 つの状態:
// 1. Rest (focused でない)
// 2. Focused (フォーカス中)
// 3. Filled (値がある)
// 4. Error (検証失敗)

<input
  type="email"
  placeholder="Email address"
  aria-describedby="email-error"
  onChange={(e) => validateEmail(e.target.value)}
/>
<span id="email-error" className="error-text">
  {error ? "Valid email required" : ""}
</span>

Label の配置

推奨順:
1. Label above input(ベスト)
   Label
   [Input field]

2. Inside placeholder(次点)
   [Input with placeholder]

3. Floating label(浮くラベル)
   フォーカス時に上に浮く(Material Design)

4. NG:右側 Label(読みづらい)

まとめ

UI/UXは、利用者が迷わず安全に目的を達成するための設計です。見た目、情報設計、操作感、アクセシビリティ、エラー処理、性能を一体で考えることで、アプリケーションの価値が伝わりやすくなります。

参考文献

公式・標準

講義・記事

解説・補助