smithery/sooom1627

design-skills

デジタル庁デザインシステム(DADS)に準拠したUI/UXデザインとアクセシブルなコンポーネント実?

Installation

$ npx skills add smithery/sooom1627 --skill design-skills

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery/sooom1627.

npx skills add smithery/sooom1627

Browse all from smithery/sooom1627

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 23,247 B
  • docs SUMMARY.md 434 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

デジタル庁デザインシステム(DADS)準拠 Design Skills

このスキルは、デジタル庁デザインシステム(DADS - Digital Agency Design System)の原則とガイドラインに準拠したUI/UXデザインとアクセシブルなコンポーネント実装を支援します。

When to Use This Skill

このスキルは以下のシナリオで使用してください:

  • デジタル庁デザインシステムに準拠したUIコンポーネントを作成する時
  • WCAG 2.2 レベルAAのアクセシビリティ要件を満たす必要がある時
  • React/Next.jsでアクセシブルなコンポーネントを実装する時
  • Figmaデザインからコードへの実装(HTML/React)を行う時
  • 政府機関・公共機関向けのWebサイト/サービスを構築する時
  • ユニバーサルデザインとインクルーシブデザインを実践する時
  • shadcn/uiとTailwind CSSを使用してDADS準拠のコンポーネントを作成する時

Core Concepts

DADS (デジタル庁デザインシステム) とは

デジタル庁デザインシステムは、以下の要素で構成されるデザインアセットです:

  1. デザイン言語: スタイリングの考え方を提供
  2. UIコンポーネント: 情報の視覚表現とインタラクションを具現化
  3. ガイドライン: ユーザビリティとアクセシビリティを踏まえた設計・実装のための指針

DADSの基本理念

「誰一人取り残されない、人に優しいデジタル化を。」

  • アクセシビリティ最優先: WCAG 2.2に準拠し、43項目の達成基準を満たす支援
  • ユニバーサルデザイン: 障害の有無、年齢、環境に関わらず利用可能
  • オープンアクセス: 行政機関だけでなく、民間事業者も無料で利用可能

提供されるリソース

  1. ガイドライン: 設計・実装の指針
  2. デザインデータ: Figmaファイル
  3. コードスニペット: HTML版およびReact版の実装例

DADS準拠の基本原則

1. アクセシビリティファースト

WCAG 2.2 レベルAA準拠

✅ 必須チェック項目:
- [ ] キーボード操作のみで全機能にアクセス可能
- [ ] スクリーンリーダーで内容が理解可能
- [ ] 色だけに依存しない情報伝達
- [ ] 十分なコントラスト比(通常テキスト 4.5:1、大きなテキスト 3:1)
- [ ] フォーカス表示が明確
- [ ] エラーメッセージが具体的
- [ ] aria属性が適切に設定されている

2. デザイントークン

カラー

/* プライマリーカラー */
--color-primary: #0057A7;        /* デジタル庁ブルー */
--color-primary-light: #E5F0F9;
--color-primary-dark: #003D75;

/* セマンティックカラー */
--color-success: #28A745;
--color-warning: #FFC107;
--color-error: #DC3545;
--color-info: #17A2B8;

/* テキストカラー */
--color-text-primary: #212529;
--color-text-secondary: #6C757D;
--color-text-disabled: #ADB5BD;

/* 背景カラー */
--color-bg-primary: #FFFFFF;
--color-bg-secondary: #F8F9FA;
--color-bg-tertiary: #E9ECEF;

タイポグラフィ

/* フォントファミリー */
--font-family-base: 'Noto Sans JP', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Yu Gothic', YuGothic, Verdana, Meiryo, sans-serif;
--font-family-mono: 'Courier New', Consolas, Monaco, monospace;

/* フォントサイズ */
--font-size-xs: 0.75rem;   /* 12px */
--font-size-sm: 0.875rem;  /* 14px */
--font-size-base: 1rem;    /* 16px */
--font-size-lg: 1.125rem;  /* 18px */
--font-size-xl: 1.25rem;   /* 20px */
--font-size-2xl: 1.5rem;   /* 24px */
--font-size-3xl: 1.875rem; /* 30px */
--font-size-4xl: 2.25rem;  /* 36px */

/* 行間 */
--line-height-tight: 1.25;
--line-height-base: 1.5;
--line-height-relaxed: 1.75;

スペーシング

--spacing-1: 0.25rem;  /* 4px */
--spacing-2: 0.5rem;   /* 8px */
--spacing-3: 0.75rem;  /* 12px */
--spacing-4: 1rem;     /* 16px */
--spacing-5: 1.25rem;  /* 20px */
--spacing-6: 1.5rem;   /* 24px */
--spacing-8: 2rem;     /* 32px */
--spacing-10: 2.5rem;  /* 40px */
--spacing-12: 3rem;    /* 48px */
--spacing-16: 4rem;    /* 64px */

3. コンポーネント設計原則

再利用性

  • 単一責任の原則: 1つのコンポーネントは1つの責任を持つ
  • Composition over Inheritance: 継承ではなく合成を優先
  • Props駆動: 状態はpropsで制御可能にする

アクセシビリティ

  • セマンティックHTML使用
  • ARIA属性の適切な利用
  • キーボードナビゲーション対応
  • フォーカス管理

パフォーマンス

  • 遅延読み込み対応
  • 最小限の再レンダリング
  • 適切なメモ化

DADS準拠のコンポーネント実装パターン

パターン1: ボタンコンポーネント

// components/ui/dads-button.tsx
import { forwardRef } from 'react';
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';

const buttonVariants = cva(
  // ベーススタイル
  'inline-flex items-center justify-center rounded-md text-base font-medium transition-colors ' +
  'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 ' +
  'disabled:pointer-events-none disabled:opacity-50',
  {
    variants: {
      variant: {
        primary: 'bg-[#0057A7] text-white hover:bg-[#003D75] focus-visible:ring-[#0057A7]',
        secondary: 'bg-white text-[#0057A7] border-2 border-[#0057A7] hover:bg-[#E5F0F9] focus-visible:ring-[#0057A7]',
        danger: 'bg-[#DC3545] text-white hover:bg-[#C82333] focus-visible:ring-[#DC3545]',
        ghost: 'hover:bg-[#F8F9FA] hover:text-[#212529] focus-visible:ring-[#0057A7]',
      },
      size: {
        sm: 'h-9 px-3 text-sm',
        md: 'h-11 px-6',
        lg: 'h-14 px-8 text-lg',
      },
    },
    defaultVariants: {
      variant: 'primary',
      size: 'md',
    },
  }
);

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean;
  isLoading?: boolean;
}

export const DadsButton = forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, asChild = false, isLoading, children, disabled, ...props }, ref) => {
    const Comp = asChild ? Slot : 'button';

    return (
      <Comp
        className={cn(buttonVariants({ variant, size, className }))}
        ref={ref}
        disabled={disabled || isLoading}
        aria-busy={isLoading}
        {...props}
      >
        {isLoading && (
          <span className="mr-2 h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" aria-hidden="true" />
        )}
        {children}
      </Comp>
    );
  }
);

DadsButton.displayName = 'DadsButton';

パターン2: フォーム入力コンポーネント

// components/ui/dads-input.tsx
import { forwardRef } from 'react';
import { cn } from '@/lib/utils';

export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  label?: string;
  error?: string;
  helperText?: string;
  isRequired?: boolean;
}

export const DadsInput = forwardRef<HTMLInputElement, InputProps>(
  ({ className, label, error, helperText, isRequired, id, ...props }, ref) => {
    const inputId = id || `input-${Math.random().toString(36).substr(2, 9)}`;
    const errorId = `${inputId}-error`;
    const helperId = `${inputId}-helper`;

    return (
      <div className="w-full">
        {label && (
          <label
            htmlFor={inputId}
            className="mb-2 block text-sm font-medium text-[#212529]"
          >
            {label}
            {isRequired && (
              <span className="ml-1 text-[#DC3545]" aria-label="必須">
                *
              </span>
            )}
          </label>
        )}

        <input
          id={inputId}
          ref={ref}
          className={cn(
            'flex h-11 w-full rounded-md border-2 border-[#DEE2E6] bg-white px-4 py-2 text-base',
            'transition-colors',
            'placeholder:text-[#ADB5BD]',
            'focus-visible:outline-none focus-visible:border-[#0057A7] focus-visible:ring-2 focus-visible:ring-[#0057A7] focus-visible:ring-offset-2',
            'disabled:cursor-not-allowed disabled:bg-[#E9ECEF] disabled:text-[#6C757D]',
            error && 'border-[#DC3545] focus-visible:border-[#DC3545] focus-visible:ring-[#DC3545]',
            className
          )}
          aria-invalid={error ? 'true' : 'false'}
          aria-describedby={cn(
            error && errorId,
            helperText && helperId
          )}
          aria-required={isRequired}
          {...props}
        />

        {error && (
          <p id={errorId} className="mt-2 text-sm text-[#DC3545]" role="alert">
            {error}
          </p>
        )}

        {helperText && !error && (
          <p id={helperId} className="mt-2 text-sm text-[#6C757D]">
            {helperText}
          </p>
        )}
      </div>
    );
  }
);

DadsInput.displayName = 'DadsInput';

パターン3: アラート/通知コンポーネント

// components/ui/dads-alert.tsx
import { forwardRef } from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { AlertCircle, CheckCircle, Info, AlertTriangle } from 'lucide-react';
import { cn } from '@/lib/utils';

const alertVariants = cva(
  'relative w-full rounded-md border-2 p-4 [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg+div]:pl-8',
  {
    variants: {
      variant: {
        info: 'border-[#17A2B8] bg-[#D1ECF1] text-[#0C5460] [&>svg]:text-[#17A2B8]',
        success: 'border-[#28A745] bg-[#D4EDDA] text-[#155724] [&>svg]:text-[#28A745]',
        warning: 'border-[#FFC107] bg-[#FFF3CD] text-[#856404] [&>svg]:text-[#FFC107]',
        error: 'border-[#DC3545] bg-[#F8D7DA] text-[#721C24] [&>svg]:text-[#DC3545]',
      },
    },
    defaultVariants: {
      variant: 'info',
    },
  }
);

const iconMap = {
  info: Info,
  success: CheckCircle,
  warning: AlertTriangle,
  error: AlertCircle,
};

export interface AlertProps
  extends React.HTMLAttributes<HTMLDivElement>,
    VariantProps<typeof alertVariants> {
  title?: string;
}

export const DadsAlert = forwardRef<HTMLDivElement, AlertProps>(
  ({ className, variant = 'info', title, children, ...props }, ref) => {
    const Icon = iconMap[variant || 'info'];

    return (
      <div
        ref={ref}
        role="alert"
        className={cn(alertVariants({ variant }), className)}
        {...props}
      >
        <Icon className="h-5 w-5" aria-hidden="true" />
        <div>
          {title && (
            <h5 className="mb-1 font-bold leading-none tracking-tight">
              {title}
            </h5>
          )}
          <div className="text-sm [&_p]:leading-relaxed">
            {children}
          </div>
        </div>
      </div>
    );
  }
);

DadsAlert.displayName = 'DadsAlert';

アクセシビリティチェックリスト

キーボードナビゲーション

✅ Tab順序が論理的
✅ フォーカス可能な要素が明確に視認可能
✅ Enterキー/Spaceキーで操作可能
✅ Escキーでモーダル・ドロップダウンを閉じられる
✅ 矢印キーでリスト・メニュー項目を移動可能
✅ フォーカストラップが適切に機能(モーダル内など)

スクリーンリーダー対応

✅ セマンティックHTMLを使用(button, nav, main, article等)
✅ aria-label / aria-labelledby で要素を適切にラベリング
✅ aria-describedby で補足説明を提供
✅ role属性が適切に設定
✅ aria-live で動的コンテンツの変更を通知
✅ 画像にalt属性を設定(装飾画像はalt="")
✅ フォームの各入力欄にlabelを関連付け

カラーコントラスト

✅ 通常テキスト: 最低4.5:1
✅ 大きなテキスト(18px以上 or 14px太字以上): 最低3:1
✅ UIコンポーネント(ボタン境界線等): 最低3:1
✅ アクティブ状態・フォーカス状態の視覚的フィードバック
✅ 色だけに依存しない情報伝達(アイコン・テキストも併用)

エラーハンドリング

✅ エラーメッセージが具体的
✅ エラー発生箇所が明確(aria-invalid, role="alert")
✅ エラー修正方法を提示
✅ エラーフィールドに自動フォーカス

テスト戦略

アクセシビリティテスト

// __tests__/accessibility.test.tsx
import { render } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';
import { DadsButton } from '@/components/ui/dads-button';

expect.extend(toHaveNoViolations);

describe('DadsButton Accessibility', () => {
  it('should not have accessibility violations', async () => {
    const { container } = render(<DadsButton>送信</DadsButton>);
    const results = await axe(container);
    expect(results).toHaveNoViolations();
  });

  it('should have proper ARIA attributes when loading', () => {
    const { getByRole } = render(<DadsButton isLoading>送信中</DadsButton>);
    const button = getByRole('button');
    expect(button).toHaveAttribute('aria-busy', 'true');
    expect(button).toBeDisabled();
  });
});

キーボードナビゲーションテスト

// __tests__/keyboard-navigation.test.tsx
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { DadsInput } from '@/components/ui/dads-input';

describe('DadsInput Keyboard Navigation', () => {
  it('should be focusable with Tab key', async () => {
    const user = userEvent.setup();
    const { getByLabelText } = render(<DadsInput label="名前" />);

    await user.tab();
    expect(getByLabelText('名前')).toHaveFocus();
  });

  it('should support keyboard input', async () => {
    const user = userEvent.setup();
    const { getByLabelText } = render(<DadsInput label="名前" />);
    const input = getByLabelText('名前');

    await user.type(input, 'テスト太郎');
    expect(input).toHaveValue('テスト太郎');
  });
});

AI Assistant Instructions

このスキルが有効化された時、以下の手順に従ってください:

1. 要件の確認

最初に以下を確認してください:
- [ ] 実装するコンポーネントの種類(ボタン、フォーム、ナビゲーション等)
- [ ] 必要なアクセシビリティレベル(通常はWCAG 2.2 レベルAA)
- [ ] 対象ブラウザ・デバイス
- [ ] 既存のプロジェクト構成(shadcn/ui使用の有無等)

2. 実装ワークフロー

  1. コンポーネント設計

- セマンティックHTMLの選択 - ARIA属性の計画 - キーボードナビゲーション仕様の定義

  1. スタイリング実装

- DADSデザイントークンを使用 - Tailwind CSSでユーティリティクラス適用 - レスポンシブデザイン対応

  1. アクセシビリティ実装

- ARIA属性の追加 - キーボードイベントハンドラー実装 - フォーカス管理

  1. テスト作成

- ユニットテスト(動作確認) - アクセシビリティテスト(jest-axe) - キーボードナビゲーションテスト

  1. ドキュメント作成

- Propsインターフェースの説明 - 使用例の提供 - アクセシビリティノートの記載

3. 常に実施すること (Always)

✅ セマンティックHTMLを優先的に使用する
✅ ARIA属性を適切に設定する(role, aria-label, aria-describedby等)
✅ キーボードナビゲーションを完全にサポートする
✅ フォーカス表示を明確にする(focus-visible)
✅ カラーコントラスト比を検証する(4.5:1以上)
✅ エラーメッセージを具体的かつ明確にする
✅ ローディング状態をaria-busyで示す
✅ 無効状態をdisabled + aria-disabledで示す
✅ TypeScript strictモードでの型安全性を確保する
✅ jest-axeでアクセシビリティテストを実行する
✅ 日本語UIに最適化する(フォント、文字サイズ等)

4. 絶対にしないこと (Never)

❌ divやspanで本来セマンティックな要素を代用しない(buttonの代わりにdiv等)
❌ 色だけで情報を伝達しない(アイコン・テキストも併用)
❌ aria属性を乱用しない(セマンティックHTMLで十分な場合は不要)
❌ tabIndexに正の値を使用しない(0, -1のみ使用)
❌ フォーカス表示を完全に削除しない(outline: noneのみは危険)
❌ アニメーションを強制しない(prefers-reduced-motionを考慮)
❌ 固定のpxサイズを多用しない(rem/emを優先)
❌ アクセシビリティテストをスキップしない
❌ 英語UIの直訳をしない(日本語として自然な表現を使用)

5. コード生成時のテンプレート

新しいコンポーネントを生成する際は、以下の構造を使用してください:

// 1. Import statements
import { forwardRef } from 'react';
import { cn } from '@/lib/utils';

// 2. Props interface with JSDoc
export interface ComponentNameProps extends React.HTMLAttributes<HTMLElement> {
  /**
   * コンポーネントの説明
   */
  propName?: string;
}

// 3. Component implementation with forwardRef
export const ComponentName = forwardRef<HTMLElement, ComponentNameProps>(
  ({ className, ...props }, ref) => {
    return (
      <element
        ref={ref}
        className={cn('base-styles', className)}
        // ARIA attributes
        role="..."
        aria-label="..."
        {...props}
      >
        {/* Content */}
      </element>
    );
  }
);

// 4. Display name for debugging
ComponentName.displayName = 'ComponentName';

6. レビュー観点

実装後、以下の観点でセルフレビューを実施してください:

**機能性**
- [ ] 仕様通りに動作する
- [ ] エッジケースを考慮している
- [ ] エラーハンドリングが適切

**アクセシビリティ**
- [ ] キーボードのみで操作可能
- [ ] スクリーンリーダーで内容を理解可能
- [ ] カラーコントラストが十分
- [ ] ARIA属性が適切
- [ ] jest-axeテストが全てパス

**パフォーマンス**
- [ ] 不要な再レンダリングがない
- [ ] メモ化が適切に使用されている
- [ ] バンドルサイズへの影響が最小限

**コード品質**
- [ ] TypeScript strictモードでエラーなし
- [ ] ESLintルールに準拠
- [ ] テストカバレッジ80%以上
- [ ] ドキュメントが充実

参考リソース

公式ドキュメント

WCAG 2.2 リソース

ツール

  • jest-axe: アクセシビリティテストライブラリ
  • axe DevTools: ブラウザ拡張機能(Chrome/Firefox)
  • WAVE: アクセシビリティ評価ツール
  • Lighthouse: 総合的な品質評価(アクセシビリティ含む)

トラブルシューティング

問題: フォーカスリングが表示されない

解決策:

/* focus-visibleを使用(キーボード操作時のみ表示) */
.element:focus-visible {
  outline: 2px solid #0057A7;
  outline-offset: 2px;
}

/* Tailwindの場合 */
className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#0057A7] focus-visible:ring-offset-2"

問題: スクリーンリーダーが要素を読み上げない

解決策:

// セマンティックHTMLを使用
<button>送信</button>  // ✅ Good

// ARIA属性で補完
<div role="button" tabIndex={0} aria-label="送信">送信</div>  // ⚠️ 最終手段

問題: カラーコントラストが不足

解決策:

// DADSカラートークンを使用すれば自動的に準拠
const textOnPrimary = 'text-white';  // #FFFFFF on #0057A7 = 8.59:1 ✅
const textOnLight = 'text-[#212529]';  // #212529 on #F8F9FA = 11.88:1 ✅

// カスタムカラーの場合は検証ツールで確認
// https://webaim.org/resources/contrastchecker/

問題: モーダルからフォーカスが抜ける

解決策:

// React Hook Formの場合
import { useEffect, useRef } from 'react';
import { useFocusTrap } from '@/hooks/use-focus-trap';

export function Modal({ isOpen, onClose, children }) {
  const modalRef = useRef<HTMLDivElement>(null);

  useFocusTrap(modalRef, isOpen);

  useEffect(() => {
    if (isOpen) {
      // 最初のフォーカス可能要素にフォーカス
      const firstFocusable = modalRef.current?.querySelector(
        'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
      );
      (firstFocusable as HTMLElement)?.focus();
    }
  }, [isOpen]);

  return (
    <div ref={modalRef} role="dialog" aria-modal="true">
      {children}
    </div>
  );
}

まとめ

このスキルを使用することで、デジタル庁デザインシステム(DADS)に準拠した高品質でアクセシブルなUIコンポーネントを効率的に実装できます。

重要なポイント:

  1. アクセシビリティを最優先に設計・実装する
  2. DADSデザイントークンを忠実に使用する
  3. セマンティックHTMLとARIA属性を適切に組み合わせる
  4. キーボードナビゲーションを完全にサポートする
  5. jest-axeで自動テストを実施する
  6. 日本語UIとして自然な表現を心がける

これらの原則に従うことで、「誰一人取り残されない、人に優しいデジタル化」を実現するWebサービスを構築できます。