mcpField
G
google-material-design

google-material-design

github.com/copyleftdevデザイン・UI2148 回インストール15 週間前に更新
リポジトリ

google-material-design について

Googleのオープンソースデザインシステムです。Material 3のテーマ、自動レイアウト、モーション、アクセシビリティなど、画面設計と実装の指針を提供します。

主な機能

  1. 01Material 3のカラーとテーマ
  2. 02コンポーネントと操作状態の指針
  3. 03アダプティブレイアウトとナビゲーション
  4. 04タイポグラフィ、形状、標高のルール
  5. 05タッチとフォーカスのアクセシビリティ指針

活用シーン

  1. 01一貫したビジュアル言語の構築
  2. 02画面サイズに応じたナビゲーション調整
  3. 03画面の色、コンポーネント、モーションの選定
  4. 04タッチ領域とキーボードフォーカスの確認

インストール

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

$npx skills add https://github.com/copyleftdev/sk1llz --skill google-material-design

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

詳細

Google Material Design⁠‍⁠​‌​‌​​‌‌‍​‌​​‌​‌‌‍​​‌‌​​​‌‍​‌​​‌‌​​‍​​​​​​​‌‍‌​​‌‌​‌​‍‌​​​​​​​‍‌‌​​‌‌‌‌‍‌‌​​​‌​​‍‌‌‌‌‌‌​‌‍‌‌​‌​​​​‍​‌​‌‌‌‌‌‍​‌​​‌​‌‌‍​‌‌​‌​​‌‍‌​‌​‌‌‌​‍​​‌​‌​​​‍‌‌‌​‌​‌‌‍‌​‌‌​​‌​‍‌​​‌​​​​‍​​​​​​‌​‍​​‌‌‌​​‌‍​​​​‌​​‌‍‌‌‌​​‌‌‌⁠‍⁠

概要

Material Design は、物理世界とデジタル世界の隔たりを埋める、Google のオープンソースデザインシステムです。2014 年に発表され、Material Design 2(2018 年)、Material Design 3(2021 年)を経て進化してきました。あらゆるプラットフォームで美しく、機能的で、アクセシブルなインターフェースを構築するための包括的なフレームワークを提供します。

参考資料

基本理念

「Material はメタファーです。」

「大胆で、グラフィカルで、意図的。」

「動きが意味をもたらします。」

「アダプティブなデザイン、心躍る体験。」

Material Design は、優れたデザインの古典的な原則と、テクノロジーや科学がもたらす革新と可能性を融合したビジュアル言語を生み出します。

Material のメタファー

物理世界からの着想
┌─────────────────────────────────────────────────────────┐
│  MATERIAL = DIGITAL PAPER + INK + PHYSICAL PROPERTIES   │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ┌─────────┐     Light                                  │
│  │ Surface │ ◄── casts shadows                          │
│  │  (z=2)  │     based on elevation                     │
│  └─────────┘                                            │
│       │                                                 │
│       │ Elevation                                       │
│       ▼                                                 │
│  ╔═════════════════════════════════════════════════╗   │
│  ║              Ground Plane (z=0)                 ║   │
│  ╚═════════════════════════════════════════════════╝   │
│                                                         │
│  • Surfaces exist in 3D space with depth               │
│  • Material has physical properties and affordances    │
│  • Shadows communicate spatial relationships           │
│  • Content is printed on material, not within it       │
└─────────────────────────────────────────────────────────┘
エレベーションシステム
Elevation (dp)    Component Examples           Shadow Intensity
─────────────────────────────────────────────────────────────
    0dp           Background, disabled         None
    1dp           Card (resting), Search bar   Subtle
    2dp           Card (raised), Button        Light
    3dp           Refresh indicator            Light-Medium
    4dp           App Bar                      Medium
    6dp           FAB (resting), Snackbar      Medium
    8dp           Bottom sheet, Menu           Pronounced
   12dp           FAB (pressed), Dialog        Strong
   16dp           Nav drawer                   Strong
   24dp           Modal bottom sheet           Maximum

デザイン原則

1. Material はメタファーです
/* Material surfaces have consistent properties */
.surface {
  /* Surfaces are opaque and cast shadows */
  background: var(--md-sys-color-surface);
  
  /* Elevation creates depth */
  box-shadow: var(--md-sys-elevation-level2);
  
  /* Corners define character */
  border-radius: var(--md-sys-shape-corner-medium);
}

/* Content rests ON material, not inside it */
.content-on-surface {
  /* Ink metaphor - content is printed on surface */
  color: var(--md-sys-color-on-surface);
}
2. 大胆で、グラフィカルで、意図的
Typography Hierarchy (Material 3)
═══════════════════════════════════════════════════════════

Display Large    57sp    Headlines for hero moments
Display Medium   45sp    Large display text
Display Small    36sp    Smaller display text
─────────────────────────────────────────────────────────
Headline Large   32sp    High-emphasis text
Headline Medium  28sp    Medium emphasis headers
Headline Small   24sp    Smaller headlines
─────────────────────────────────────────────────────────
Title Large      22sp    Prominent titles
Title Medium     16sp    Medium titles
Title Small      14sp    Smaller titles, tabs
─────────────────────────────────────────────────────────
Label Large      14sp    Buttons, prominent labels
Label Medium     12sp    Navigation labels
Label Small      11sp    Timestamps, hints
─────────────────────────────────────────────────────────
Body Large       16sp    Primary body text
Body Medium      14sp    Secondary body text
Body Small       12sp    Captions, annotations
3. 動きが意味をもたらします
// motion_principles.js
// Motion in Material Design is choreographed and meaningful

const MaterialMotion = {
  // Duration tokens (Material 3)
  duration: {
    short1: 50,    // Micro-interactions
    short2: 100,   // Simple selections
    short3: 150,   // Checkbox, switch
    short4: 200,   // Small expand/collapse
    medium1: 250,  // Standard enter/exit
    medium2: 300,  // Card expand
    medium3: 350,  // Complex animations
    medium4: 400,  // Page transitions
    long1: 450,    // Large area transitions
    long2: 500,    // Complex choreography
    long3: 550,    // Elaborate animations
    long4: 600,    // Maximum duration
  },
  
  // Easing curves
  easing: {
    // Emphasized - for prominent elements
    emphasized: 'cubic-bezier(0.2, 0.0, 0, 1.0)',
    emphasizedDecelerate: 'cubic-bezier(0.05, 0.7, 0.1, 1.0)',
    emphasizedAccelerate: 'cubic-bezier(0.3, 0.0, 0.8, 0.15)',
    
    // Standard - for most transitions
    standard: 'cubic-bezier(0.2, 0.0, 0, 1.0)',
    standardDecelerate: 'cubic-bezier(0, 0, 0, 1)',
    standardAccelerate: 'cubic-bezier(0.3, 0, 1, 1)',
  },
  
  // Transition patterns
  patterns: {
    // Container transform - element becomes new screen
    containerTransform: {
      use: 'Element expands into a new screen',
      duration: 'medium2',
      easing: 'emphasized',
    },
    
    // Shared axis - elements share spatial relationship
    sharedAxis: {
      use: 'Navigation between related destinations',
      duration: 'medium1',
      easing: 'emphasized',
    },
    
    // Fade through - unrelated content transition
    fadeThrough: {
      use: 'Switching between unrelated content',
      duration: 'medium1',
      easing: 'emphasized',
    },
    
    // Fade - simple appearance/disappearance
    fade: {
      use: 'UI elements appearing or disappearing',
      duration: 'short4',
      easing: 'standard',
    },
  },
};

// Example: Container transform animation
function containerTransform(element, options) {
  const { duration, easing } = MaterialMotion.patterns.containerTransform;
  
  return element.animate([
    { transform: 'scale(0.85)', opacity: 0 },
    { transform: 'scale(1)', opacity: 1 },
  ], {
    duration: MaterialMotion.duration[duration],
    easing: MaterialMotion.easing[easing],
    fill: 'forwards',
  });
}

カラーシステム(Material 3 のダイナミックカラー)

トーナルパレット
Source Color → Tonal Palette → Color Scheme
═══════════════════════════════════════════════════════════

Primary Source (#6750A4)
    │
    ▼
┌─────────────────────────────────────────────────────────┐
│ Tonal Palette: Primary                                  │
│ ─────────────────────────────────────────────────────── │
│ 0    10   20   30   40   50   60   70   80   90   95 100│
│ ▓▓▓▓ ▓▓▓▓ ▓▓▓▓ ▓▓▓▓ ████ ████ ░░░░ ░░░░ ░░░░ ░░░░ ░░░░ │
│ Darkest ◄─────────────────────────────────► Lightest   │
└─────────────────────────────────────────────────────────┘
    │
    ▼
Light Scheme:               Dark Scheme:
  primary: tone40             primary: tone80
  onPrimary: tone100          onPrimary: tone20
  primaryContainer: tone90    primaryContainer: tone30
  onPrimaryContainer: tone10  onPrimaryContainer: tone90
色の役割
/* Material 3 Color Tokens */
:root {
  /* Primary - Key color, brand identity */
  --md-sys-color-primary: #6750A4;
  --md-sys-color-on-primary: #FFFFFF;
  --md-sys-color-primary-container: #EADDFF;
  --md-sys-color-on-primary-container: #21005D;
  
  /* Secondary - Supporting color */
  --md-sys-color-secondary: #625B71;
  --md-sys-color-on-secondary: #FFFFFF;
  --md-sys-color-secondary-container: #E8DEF8;
  --md-sys-color-on-secondary-container: #1D192B;
  
  /* Tertiary - Accent, contrast */
  --md-sys-color-tertiary: #7D5260;
  --md-sys-color-on-tertiary: #FFFFFF;
  --md-sys-color-tertiary-container: #FFD8E4;
  --md-sys-color-on-tertiary-container: #31111D;
  
  /* Error - Alerts and errors */
  --md-sys-color-error: #B3261E;
  --md-sys-color-on-error: #FFFFFF;
  --md-sys-color-error-container: #F9DEDC;
  --md-sys-color-on-error-container: #410E0B;
  
  /* Surface - Backgrounds */
  --md-sys-color-surface: #FFFBFE;
  --md-sys-color-on-surface: #1C1B1F;
  --md-sys-color-surface-variant: #E7E0EC;
  --md-sys-color-on-surface-variant: #49454F;
  
  /* Outline - Borders, dividers */
  --md-sys-color-outline: #79747E;
  --md-sys-color-outline-variant: #CAC4D0;
}
コンテンツからのダイナミックカラー
// dynamic_color.js
// Extract color scheme from images or user preference

class MaterialColorScheme {
  constructor(sourceColor) {
    this.source = sourceColor;
    this.palette = this.generateTonalPalette(sourceColor);
  }
  
  generateTonalPalette(color) {
    // HCT (Hue, Chroma, Tone) color space for perceptual uniformity
    const hct = rgbToHct(color);
    
    return {
      0: hctToRgb({ ...hct, tone: 0 }),
      10: hctToRgb({ ...hct, tone: 10 }),
      20: hctToRgb({ ...hct, tone: 20 }),
      30: hctToRgb({ ...hct, tone: 30 }),
      40: hctToRgb({ ...hct, tone: 40 }),
      50: hctToRgb({ ...hct, tone: 50 }),
      60: hctToRgb({ ...hct, tone: 60 }),
      70: hctToRgb({ ...hct, tone: 70 }),
      80: hctToRgb({ ...hct, tone: 80 }),
      90: hctToRgb({ ...hct, tone: 90 }),
      95: hctToRgb({ ...hct, tone: 95 }),
      99: hctToRgb({ ...hct, tone: 99 }),
      100: hctToRgb({ ...hct, tone: 100 }),
    };
  }
  
  getLightScheme() {
    return {
      primary: this.palette[40],
      onPrimary: this.palette[100],
      primaryContainer: this.palette[90],
      onPrimaryContainer: this.palette[10],
      // ... remaining roles
    };
  }
  
  getDarkScheme() {
    return {
      primary: this.palette[80],
      onPrimary: this.palette[20],
      primaryContainer: this.palette[30],
      onPrimaryContainer: this.palette[90],
      // ... remaining roles
    };
  }
}

コンポーネントアーキテクチャ

Material コンポーネントの構成
┌─────────────────────────────────────────────────────────┐
│                    BUTTON ANATOMY                       │
├─────────────────────────────────────────────────────────┤
│                                                         │
│    ┌─────────────────────────────────────────┐         │
│    │  [Icon]  Label Text                     │         │
│    └─────────────────────────────────────────┘         │
│    │         │                               │         │
│    │         │                               │         │
│    │    Leading Icon (optional)              │         │
│    │                                         │         │
│    │    Label text (required)           Container      │
│    │                                    (required)     │
│    │                                         │         │
│    └─────────────────────────────────────────┘         │
│                                                         │
│    States: Enabled, Disabled, Hovered,                 │
│            Focused, Pressed                            │
└─────────────────────────────────────────────────────────┘
ボタンのバリエーション
// MaterialButton.tsx
// Material 3 button implementations

interface ButtonProps {
  variant: 'elevated' | 'filled' | 'tonal' | 'outlined' | 'text';
  icon?: React.ReactNode;
  children: React.ReactNode;
  disabled?: boolean;
  onClick?: () => void;
}

// Elevated - For secondary actions requiring emphasis
const ElevatedButton = styled.button`
  background: var(--md-sys-color-surface-container-low);
  color: var(--md-sys-color-primary);
  box-shadow: var(--md-sys-elevation-level1);
  border: none;
  border-radius: 20px;
  padding: 10px 24px;
  font: var(--md-sys-typescale-label-large);
  
  &:hover {
    box-shadow: var(--md-sys-elevation-level2);
    background: color-mix(
      in srgb,
      var(--md-sys-color-primary) 8%,
      var(--md-sys-color-surface-container-low)
    );
  }
  
  &:focus-visible {
    outline: none;
    box-shadow: var(--md-sys-elevation-level1);
    background: color-mix(
      in srgb,
      var(--md-sys-color-primary) 12%,
      var(--md-sys-color-surface-container-low)
    );
  }
  
  &:active {
    box-shadow: var(--md-sys-elevation-level1);
    background: color-mix(
      in srgb,
      var(--md-sys-color-primary) 12%,
      var(--md-sys-color-surface-container-low)
    );
  }
`;

// Filled - For primary actions, high emphasis
const FilledButton = styled.button`
  background: var(--md-sys-color-primary);
  color: var(--md-sys-color-on-primary);
  border: none;
  border-radius: 20px;
  padding: 10px 24px;
  
  &:hover {
    box-shadow: var(--md-sys-elevation-level1);
    background: color-mix(
      in srgb,
      var(--md-sys-color-on-primary) 8%,
      var(--md-sys-color-primary)
    );
  }
`;

// Tonal - For secondary actions, medium emphasis
const TonalButton = styled.button`
  background: var(--md-sys-color-secondary-container);
  color: var(--md-sys-color-on-secondary-container);
  border: none;
  border-radius: 20px;
  padding: 10px 24px;
`;

// Outlined - For secondary actions, low emphasis
const OutlinedButton = styled.button`
  background: transparent;
  color: var(--md-sys-color-primary);
  border: 1px solid var(--md-sys-color-outline);
  border-radius: 20px;
  padding: 10px 24px;
`;

// Text - For lowest emphasis actions
const TextButton = styled.button`
  background: transparent;
  color: var(--md-sys-color-primary);
  border: none;
  border-radius: 20px;
  padding: 10px 12px;
`;
状態レイヤーシステム
/* Material 3 State Layer Implementation */

.interactive-element {
  position: relative;
  overflow: hidden;
}

/* State layer overlay */
.interactive-element::before {
  content: '';
  position: absolute;
  inset: 0;
  background: currentColor;
  opacity: 0;
  transition: opacity 150ms var(--md-sys-motion-easing-standard);
}

/* State layer opacities */
.interactive-element:hover::before {
  opacity: 0.08;  /* Hover state */
}

.interactive-element:focus-visible::before {
  opacity: 0.12;  /* Focus state */
}

.interactive-element:active::before {
  opacity: 0.12;  /* Pressed state */
}

.interactive-element[data-dragged]::before {
  opacity: 0.16;  /* Dragged state */
}

/* Disabled states - no state layer, reduced opacity */
.interactive-element:disabled {
  opacity: 0.38;
  pointer-events: none;
}

.interactive-element:disabled::before {
  display: none;
}

形状システム

コーナーのスタイル
Shape Scale (Material 3)
═══════════════════════════════════════════════════════════

None (0dp)        Extra Small (4dp)    Small (8dp)
┌──────────┐      ╭──────────╮        ╭──────────╮
│          │      │          │        │          │
│          │      │          │        │          │
│          │      │          │        │          │
└──────────┘      ╰──────────╯        ╰──────────╯

Medium (12dp)     Large (16dp)        Extra Large (28dp)
╭──────────╮      ╭──────────╮        ╭──────────╮
│          │      │          │        │          │
│          │      │          │        │          │
│          │      │          │        │          │
╰──────────╯      ╰──────────╯        ╰──────────╯

Full (50%)
    ╭────╮
   │      │
   │      │
    ╰────╯
/* Shape tokens */
:root {
  --md-sys-shape-corner-none: 0px;
  --md-sys-shape-corner-extra-small: 4px;
  --md-sys-shape-corner-small: 8px;
  --md-sys-shape-corner-medium: 12px;
  --md-sys-shape-corner-large: 16px;
  --md-sys-shape-corner-extra-large: 28px;
  --md-sys-shape-corner-full: 9999px;
}

/* Shape application by component */
.chip { border-radius: var(--md-sys-shape-corner-small); }
.card { border-radius: var(--md-sys-shape-corner-medium); }
.dialog { border-radius: var(--md-sys-shape-corner-extra-large); }
.fab { border-radius: var(--md-sys-shape-corner-large); }
.button { border-radius: var(--md-sys-shape-corner-full); }

アダプティブレイアウト

レスポンシブグリッドシステム
Window Size Classes
═══════════════════════════════════════════════════════════

Compact (< 600dp)     Medium (600-839dp)   Expanded (840dp+)
┌─────────────┐       ┌────────────────┐   ┌───────────────────┐
│             │       │                │   │ Nav │             │
│   Content   │       │    Content     │   │ Bar │   Content   │
│             │       │                │   │     │             │
│             │       │                │   │     │             │
├─────────────┤       │                │   │     │             │
│  Nav Bar    │       ├────────────────┤   │     │             │
└─────────────┘       │    Nav Bar     │   │     │             │
                      └────────────────┘   └───────────────────┘
4 columns             8 columns (body)     12 columns (body)
16dp margins          24dp margins         24dp margins
8dp gutters           16dp gutters         24dp gutters
レイアウトの実装
// AdaptiveLayout.tsx
// Material 3 adaptive scaffold

import { useWindowSize } from './hooks';

type WindowSizeClass = 'compact' | 'medium' | 'expanded' | 'large' | 'extraLarge';

function getWindowSizeClass(width: number): WindowSizeClass {
  if (width < 600) return 'compact';
  if (width < 840) return 'medium';
  if (width < 1200) return 'expanded';
  if (width < 1600) return 'large';
  return 'extraLarge';
}

interface AdaptiveLayoutProps {
  navigation: React.ReactNode;
  content: React.ReactNode;
  detail?: React.ReactNode;
}

export function AdaptiveLayout({ navigation, content, detail }: AdaptiveLayoutProps) {
  const { width } = useWindowSize();
  const sizeClass = getWindowSizeClass(width);
  
  // Compact: Bottom navigation bar
  if (sizeClass === 'compact') {
    return (
      <div className="layout-compact">
        <main className="content">{content}</main>
        <nav className="navigation-bar">{navigation}</nav>
      </div>
    );
  }
  
  // Medium: Navigation rail
  if (sizeClass === 'medium') {
    return (
      <div className="layout-medium">
        <nav className="navigation-rail">{navigation}</nav>
        <main className="content">{content}</main>
      </div>
    );
  }
  
  // Expanded+: Navigation drawer with optional detail pane
  return (
    <div className="layout-expanded">
      <nav className="navigation-drawer">{navigation}</nav>
      <main className="content">{content}</main>
      {detail && sizeClass !== 'expanded' && (
        <aside className="detail-pane">{detail}</aside>
      )}
    </div>
  );
}
ナビゲーションパターン
Navigation Components by Screen Size
═══════════════════════════════════════════════════════════

           Compact         Medium          Expanded
           ─────────────────────────────────────────────
Primary    Bottom Bar      Rail            Drawer
           3-5 items       3-7 items       Full labels

Secondary  Modal Drawer    Modal Drawer    Persistent Drawer

Tertiary   Tabs           Tabs            Tabs
           Bottom Sheet   Side Sheet      Side Sheet

アクセシビリティ(A11y)

タッチターゲット
/* Minimum touch target: 48x48dp */
.touch-target {
  min-width: 48px;
  min-height: 48px;
  
  /* Visual element can be smaller, but touch area must be 48dp */
  display: flex;
  align-items: center;
  justify-content: center;
}

.icon-button {
  /* Icon is 24dp, but touch target is 48dp */
  width: 48px;
  height: 48px;
  padding: 12px;
}

.icon-button svg {
  width: 24px;
  height: 24px;
}
色のコントラスト
Contrast Requirements (WCAG 2.1)
═══════════════════════════════════════════════════════════

Text Size        Minimum (AA)    Enhanced (AAA)
─────────────────────────────────────────────────
Normal text      4.5:1           7:1
Large text       3:1             4.5:1
(18sp+ or 14sp bold)

UI Components    3:1             Not defined
(borders, icons)

Material Design ensures:
• on-primary meets 4.5:1 against primary
• on-surface meets 4.5:1 against surface
• All semantic color pairs are accessible
フォーカスインジケーター
/* Material 3 focus indicators */
.focusable {
  outline: none;
  position: relative;
}

.focusable:focus-visible {
  /* Focus ring using secondary color */
  outline: 3px solid var(--md-sys-color-secondary);
  outline-offset: 2px;
}

/* Alternative: Focus indicator within element */
.button:focus-visible {
  outline: none;
}

.button:focus-visible::after {
  content: '';
  position: absolute;
  inset: -4px;
  border: 3px solid var(--md-sys-color-secondary);
  border-radius: inherit;
  pointer-events: none;
}

実装時の指針

必ず行うこと
  • 色、タイポグラフィ、形状にはデザイントークンシステムを使用する
  • すべてのインタラクティブな状態(有効、無効、ホバー、フォーカス、押下)を実装する
  • 最小 48dp のタッチターゲットを確保する
  • 視認可能なフォーカスインジケーターを用意する
  • 生の値ではなく、セマンティックな色の役割(primary、secondary、error)を使用する
  • 空間的な階層にはエレベーションシステムを適用する
  • 状態の変化に意味のある動きを取り入れる
決して行わないこと
  • トークンの代わりに色の値をハードコードする
  • 無効状態を無視する
  • トーナルパレット以外のカスタムカラーを作成する
  • キーボードユーザー向けのフォーカス状態を省略する
  • 目的なくエレベーションを使用する
  • 意味を伝えない動きを追加する
  • タッチターゲットの最小サイズに違反する
優先すること
  • 主要なアクションには塗りつぶしボタン、三次アクションにはテキストボタンを使用する
  • 一貫性があり調和のとれた色にはトーナルパレットを使用する
  • ほとんどのトランジションには標準のイージングを使用する
  • オーバーレイには不透明度ではなくサーフェスの色のバリエーションを使用する
  • カスタム実装よりコンポーネントライブラリを使用する
  • 固定ブレークポイントよりアダプティブレイアウトを使用する

メンタルモデル

Material Design では、次の点を確認します。

  1. Material らしいか? 奥行きのある物理的な表面のように振る舞いますか?
  2. 意図的か? すべての要素に明確な目的がありますか?
  3. 意味があるか? 動きが状態を伝え、注意を導きますか?
  4. アクセシブルか? 能力にかかわらず、誰もが利用できますか?
  5. アダプティブか? あらゆる画面サイズやデバイスで動作しますか?
  6. 心躍るか? ポジティブな感情を生み出しますか?

特徴的なマテリアルの動き

  • ダイナミックカラー:ユーザーコンテンツに基づくパーソナライズされたテーマ
  • トーナルパレット:単一の色源から生まれる調和のとれたカラー
  • ステートレイヤー:オーバーレイによる一貫したインタラクションフィードバック
  • コンテナートランスフォーム:要素から画面へのシームレスな遷移
  • エレベーション階層:重要度を伝えるシャドウ
  • アダプティブスキャフォールド:画面サイズに応じて変化するナビゲーション
  • タイポグラフィスケール:コンテンツの明確な階層を示す15種類のスタイル
  • シェイプシステム:コンポーネントの個性を伝える角の形状

コメント