UI設計における視覚的階層とゲシュタルト原則

Shunku

はじめに

優れたインターフェースは、ユーザーがすべての単語を読んだり、どこを見るべきか考えたりする必要がありません。視覚的階層とゲシュタルト原則を通じて、ユーザーの注意を誘導し、構造を瞬時に伝えることができます。

視覚的階層は、ユーザーが最初に見るもの、次に見るもの、その次に見るものを決定します。ゲシュタルト原則は、人間がどのように視覚要素を自然にグループ化し、解釈するかを説明します。これらが組み合わさって、効果的なレイアウト設計の基盤を形成します。

視覚的階層

視覚的階層とは、要素を重要度順に配置することです。ユーザーはレイアウトだけで情報構造を推測できるべきです。

何が物事を重要に見せるか?

いくつかの視覚的特性が認識される重要性に寄与します:

flowchart TB
    subgraph HighImportance["高い視覚的重み"]
        A["大きなサイズ"]
        B["太い/重いウェイト"]
        C["高いコントラスト"]
        D["彩度の高い色"]
        E["前景の位置"]
    end

    subgraph LowImportance["低い視覚的重み"]
        F["小さなサイズ"]
        G["軽いウェイト"]
        H["低いコントラスト"]
        I["くすんだ色"]
        J["背景の位置"]
    end

    style HighImportance fill:#3b82f6,color:#fff
    style LowImportance fill:#94a3b8,color:#fff

視覚的重みの要素

要素 高い重要性 低い重要性
サイズ 大きい 小さい
ウェイト 太い 細い
彩度が高い、暖色 くすんだ、寒色
コントラスト 背景との差が大きい 背景との差が小さい
位置 上、左、中央 下、端
余白 周囲にスペース 詰まっている
詳細度 複雑、詳細 シンプル、無地

CSSでの階層作成

/* 主見出し - 最も高い視覚的重み */
.heading-primary {
  font-size: 2.5rem;
  font-weight: 800;
  color: #111827;
  line-height: 1.2;
}

/* 副見出し */
.heading-secondary {
  font-size: 1.75rem;
  font-weight: 700;
  color: #1f2937;
  line-height: 1.3;
}

/* 第三レベルの見出し */
.heading-tertiary {
  font-size: 1.25rem;
  font-weight: 600;
  color: #374151;
  line-height: 1.4;
}

/* 本文 - 最も低い階層レベル */
.body-text {
  font-size: 1rem;
  font-weight: 400;
  color: #4b5563;
  line-height: 1.6;
}

/* 控えめなテキスト */
.text-muted {
  font-size: 0.875rem;
  font-weight: 400;
  color: #9ca3af;
}

実践での階層

function ProductCard({ product }) {
  return (
    <article className="p-6 bg-white rounded-lg shadow">
      {/* 主要: 商品名 - 最大、最も太い */}
      <h2 className="text-xl font-bold text-gray-900">
        {product.name}
      </h2>

      {/* 副次: 価格 - 目立つがより小さい */}
      <p className="mt-2 text-lg font-semibold text-blue-600">
        ¥{product.price}
      </p>

      {/* 第三: 説明 - 通常のウェイト */}
      <p className="mt-2 text-gray-600">
        {product.description}
      </p>

      {/* 第四: メタデータ - 最小、控えめ */}
      <p className="mt-4 text-sm text-gray-400">
        SKU: {product.sku} • 在庫 {product.stock}
      </p>

      {/* アクション: 高コントラストのボタン */}
      <button className="mt-4 w-full py-2 bg-blue-600 text-white
                         font-medium rounded hover:bg-blue-700">
        カートに追加
      </button>
    </article>
  );
}

ゲシュタルト原則

ゲシュタルト心理学は、人間がどのようにパターンを認識し、視覚情報を整理するかを説明します。これらの原則はインターフェース設計の基本です。

1. 近接(Proximity)

近くにある要素はグループとして認識されます。

flowchart LR
    subgraph Grouped["グループとして認識"]
        direction TB
        A["●"] ~~~ B["●"]
        C["●"] ~~~ D["●"]
    end

    subgraph Separate["別々に認識"]
        direction TB
        E["●"]
        F["●"]
        G["●"]
        H["●"]
    end

実装:

function FormSection() {
  return (
    <form>
      {/* 配送フィールド - 近接でグループ化 */}
      <div className="space-y-3 mb-8">
        <h3 className="font-semibold">配送先住所</h3>
        <input placeholder="住所" />
        <input placeholder="市区町村" />
        <input placeholder="郵便番号" />
      </div>

      {/* 支払いフィールド - 間隔で別グループ */}
      <div className="space-y-3 mb-8">
        <h3 className="font-semibold">支払い情報</h3>
        <input placeholder="カード番号" />
        <input placeholder="有効期限" />
        <input placeholder="セキュリティコード" />
      </div>
    </form>
  );
}
/* 一貫した間隔でグループを示す */
.form-group {
  margin-bottom: 2rem; /* グループ間は大きな間隔 */
}

.form-group > * + * {
  margin-top: 0.75rem; /* グループ内は小さな間隔 */
}

2. 類似(Similarity)

見た目が似ている要素は関連していると認識されます。

function StatusIndicators() {
  return (
    <div className="flex gap-4">
      {/* 同じスタイル = 同じカテゴリ */}
      <span className="px-2 py-1 bg-green-100 text-green-800 rounded">
        アクティブ
      </span>
      <span className="px-2 py-1 bg-green-100 text-green-800 rounded">
        確認済み
      </span>

      {/* 異なるスタイル = 異なるカテゴリ */}
      <span className="px-2 py-1 bg-red-100 text-red-800 rounded">
        期限超過
      </span>
    </div>
  );
}

function NavigationMenu() {
  return (
    <nav>
      {/* メインナビ - 同じスタイル */}
      <div className="flex gap-4 text-gray-900 font-medium">
        <a href="/">ホーム</a>
        <a href="/products">製品</a>
        <a href="/about">会社概要</a>
      </div>

      {/* ユーティリティナビ - 異なるスタイルで異なる目的を示す */}
      <div className="flex gap-4 text-gray-500 text-sm">
        <a href="/help">ヘルプ</a>
        <a href="/contact">お問い合わせ</a>
      </div>
    </nav>
  );
}

3. 閉合(Closure)

脳は不完全な形を完成させ、全体として認識します。

// 完全な境界線がなくてもカードは完全なユニットとして認識される
function Card({ children }) {
  return (
    <div className="bg-white rounded-lg shadow-sm p-4">
      {/* 影と背景が暗黙の境界を作る */}
      {children}
    </div>
  );
}

// 進捗インジケーターは閉合を使用
function ProgressRing({ progress }) {
  const circumference = 2 * Math.PI * 40;
  const offset = circumference - (progress / 100) * circumference;

  return (
    <svg width="100" height="100">
      {/* 背景の円が閉合のコンテキストを提供 */}
      <circle
        cx="50" cy="50" r="40"
        fill="none"
        stroke="#e5e7eb"
        strokeWidth="8"
      />
      {/* 部分的な弧 - 脳が円を完成させる */}
      <circle
        cx="50" cy="50" r="40"
        fill="none"
        stroke="#3b82f6"
        strokeWidth="8"
        strokeDasharray={circumference}
        strokeDashoffset={offset}
        transform="rotate(-90 50 50)"
      />
    </svg>
  );
}

4. 連続(Continuity)

線やカーブ上に配置された要素は関連していると認識されます。

function StepIndicator({ steps, currentStep }) {
  return (
    <div className="flex items-center">
      {steps.map((step, index) => (
        <React.Fragment key={step.id}>
          {/* ステップの円 */}
          <div className={`
            w-8 h-8 rounded-full flex items-center justify-center
            ${index <= currentStep
              ? 'bg-blue-600 text-white'
              : 'bg-gray-200 text-gray-600'
            }
          `}>
            {index + 1}
          </div>

          {/* 連結線が連続性を作る */}
          {index < steps.length - 1 && (
            <div className={`
              h-1 w-16
              ${index < currentStep ? 'bg-blue-600' : 'bg-gray-200'}
            `} />
          )}
        </React.Fragment>
      ))}
    </div>
  );
}

// パンくずリストは連続性を使用
function Breadcrumbs({ items }) {
  return (
    <nav className="flex items-center gap-2 text-sm">
      {items.map((item, index) => (
        <React.Fragment key={item.href}>
          <a href={item.href} className="text-gray-600 hover:text-gray-900">
            {item.label}
          </a>
          {/* 区切り文字が視覚的な連続性を作る */}
          {index < items.length - 1 && (
            <span className="text-gray-400">/</span>
          )}
        </React.Fragment>
      ))}
    </nav>
  );
}

5. 共通領域(Common Region)

境界内の要素はグループとして認識されます。

function FeatureComparison({ plans }) {
  return (
    <div className="grid grid-cols-3 gap-4">
      {plans.map(plan => (
        // 各カードが共通領域を作る
        <div
          key={plan.id}
          className="border rounded-lg p-6 bg-white"
        >
          <h3 className="text-xl font-bold">{plan.name}</h3>
          <p className="text-3xl font-bold mt-2">¥{plan.price}</p>

          {/* 機能用のネストした領域 */}
          <div className="mt-4 p-4 bg-gray-50 rounded">
            <ul className="space-y-2">
              {plan.features.map(feature => (
                <li key={feature}>{feature}</li>
              ))}
            </ul>
          </div>

          <button className="mt-4 w-full btn-primary">
            {plan.name}を選択
          </button>
        </div>
      ))}
    </div>
  );
}

6. 図と地(Figure-Ground)

ユーザーは前景(図)と背景(地)を区別します。

// モーダルは図と地の分離を使用
function Modal({ isOpen, onClose, children }) {
  if (!isOpen) return null;

  return (
    <>
      {/* 地: 暗くした背景 */}
      <div
        className="fixed inset-0 bg-black/50"
        onClick={onClose}
      />

      {/* 図: 浮き上がったモーダル */}
      <div className="fixed inset-0 flex items-center justify-center">
        <div className="bg-white rounded-lg shadow-xl p-6 max-w-md
                        relative z-10">
          {children}
        </div>
      </div>
    </>
  );
}

// ドロップダウンは図と地を使用
function Dropdown({ trigger, items }) {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div className="relative">
      <button onClick={() => setIsOpen(!isOpen)}>
        {trigger}
      </button>

      {isOpen && (
        <>
          {/* 地: クリックで閉じる領域 */}
          <div
            className="fixed inset-0"
            onClick={() => setIsOpen(false)}
          />

          {/* 図: 浮き上がったドロップダウンメニュー */}
          <div className="absolute top-full mt-2 bg-white rounded-lg
                          shadow-lg border z-10">
            {items.map(item => (
              <button
                key={item.id}
                className="block w-full px-4 py-2 text-left
                           hover:bg-gray-100"
              >
                {item.label}
              </button>
            ))}
          </div>
        </>
      )}
    </div>
  );
}

視線の流れ

視線の流れとは、ユーザーの目がデザインを通って自然にたどるパスです。

テキストが多いページのFパターン

flowchart TB
    subgraph FPattern["Fパターンの読み方"]
        A["→ → → → → → →"] --> B["↓"]
        B --> C["→ → → →"] --> D["↓"]
        D --> E["→ →"] --> F["↓"]
        F --> G["↓ ↓ ↓"]
    end

    style FPattern fill:#3b82f6,color:#fff
function ArticlePage({ article }) {
  return (
    <article>
      {/* 上部: 全幅の見出しが水平スキャンをキャッチ */}
      <h1 className="text-3xl font-bold">{article.title}</h1>

      {/* 2回目のスキャン: 最初の段落に重要な情報 */}
      <p className="text-lg text-gray-600 mt-4">
        {article.summary}
      </p>

      {/* コンテンツ: 各セクションの先頭に重要な情報 */}
      {article.sections.map(section => (
        <section key={section.id} className="mt-8">
          <h2 className="text-xl font-semibold">{section.title}</h2>
          <p className="mt-2">{section.content}</p>
        </section>
      ))}
    </article>
  );
}

コンテンツが少ないページのZパターン

flowchart LR
    A["ロゴ"] --> B["ナビゲーション"]
    B --> C["↘"]
    C --> D["ヒーロー画像"]
    D --> E["↘"]
    E --> F["CTAボタン"]

    style A fill:#3b82f6,color:#fff
    style F fill:#22c55e,color:#fff
function LandingPage() {
  return (
    <div className="min-h-screen">
      {/* 上部バーはZの開始に沿う */}
      <header className="flex justify-between items-center p-4">
        <Logo />                    {/* 左上: Zの開始 */}
        <Navigation />              {/* 右上: Zが右に移動 */}
      </header>

      {/* ヒーローはZの対角線に沿う */}
      <main className="flex flex-col items-center py-20">
        <h1 className="text-5xl font-bold text-center">
          ワークフローを変革する
        </h1>

        <p className="mt-4 text-xl text-gray-600 text-center max-w-2xl">
          モダンなチームのためのオールインワンプラットフォーム
        </p>

        {/* CTAはZの終点 - 右下エリア */}
        <button className="mt-8 px-8 py-4 bg-blue-600 text-white
                           text-lg font-semibold rounded-lg">
          無料で始める
        </button>
      </main>
    </div>
  );
}

段階的開示

まず重要な情報だけを表示し、詳細は必要に応じて利用可能にします。

function ProductDetails({ product }) {
  const [showSpecs, setShowSpecs] = useState(false);

  return (
    <div>
      {/* レベル1: 必須情報は常に表示 */}
      <h1>{product.name}</h1>
      <p className="text-2xl font-bold">¥{product.price}</p>
      <p>{product.shortDescription}</p>

      {/* レベル2: 展開可能な詳細 */}
      <button
        onClick={() => setShowSpecs(!showSpecs)}
        className="mt-4 text-blue-600"
      >
        仕様を{showSpecs ? '隠す' : '表示'}
      </button>

      {showSpecs && (
        <table className="mt-4">
          {Object.entries(product.specs).map(([key, value]) => (
            <tr key={key}>
              <td className="font-medium pr-4">{key}</td>
              <td>{value}</td>
            </tr>
          ))}
        </table>
      )}

      {/* レベル3: 完全なドキュメントへのリンク */}
      <a href={`/products/${product.id}/docs`} className="block mt-4">
        完全なドキュメントを見る →
      </a>
    </div>
  );
}

まとめ

原則 重要な洞察 設計への適用
視覚的階層 サイズ、ウェイト、色が重要性を示す 主要コンテンツを最大かつ最も太く
近接 近い = 関連 関連要素をグループ化、無関係なものを分離
類似 同じスタイル = 同じ意味 関連要素に一貫したスタイルを使用
閉合 脳が形を完成させる カード、影で暗黙の境界を使用
連続 線が目を導く ステップ、パンくずで連結を使用
共通領域 境界がグループ化 コンテナでコンテンツをグループ化
図と地 前景が目立つ モーダルコンテンツにオーバーレイ、影を使用
視線の流れ 目はパターンに従う F/Zパターンで重要コンテンツを配置

これらの原則は連携して機能します。優れた設計では、近接と共通領域で関連コンテンツをグループ化し、類似で関係を示し、視覚的階層で重要性を示し、視線の流れで意図したパスにユーザーを導きます。

参考文献

  • Tidwell, Jenifer, et al. "Designing Interfaces" (3rd Edition), Chapters 4-5
  • Bradley, Steven. "Design Fundamentals: Elements, Attributes, and Principles"
  • Gestalt Principles - Laws of UX
  • Nielsen Norman Group - Visual Hierarchy