FigmaNavi

Figmaデザインのアクセシビリティを検証するプラグイン。AIは使用せず、WCAG基準に基づいたプログラムによる判定を行います。

100% Programmatic - No AI
コントラスト比
代替テキスト
タッチターゲット
フォーカス順序
Aa
PASS
21:1
Aa
FAIL
2.1:1

判定の仕組み

FigmaNaviは、AIや機械学習を一切使用していません。すべての判定はWCAG(Web Content Accessibility Guidelines)の基準に基づき、数学的な計算とルールベースのロジックで行われます。

透明性を重視し、各チェック項目がどのように判定されているかを完全に公開しています。

検出する問題

チェック項目 判定基準 重大度
コントラスト不足 WCAG 2.1 コントラスト比 Error
Alt未設定 画像のpluginData確認 Warning
タッチターゲット小 48×48px未満 Warning
フォーカス順序未設定 pluginData確認 Info
コントラスト比チェック
WCAG 2.1 Success Criterion 1.4.3

ロービジョン(弱視)のユーザーや、明るい屋外でスマートフォンを使用する場合でもテキストを読めるように、文字色と背景色の明度差を検証します。コントラストが不十分だと、多くのユーザーにとって読みづらいUIになります。

相対輝度の計算

コントラスト比を求めるために、まず各色の相対輝度(Relative Luminance)を計算します。

L = 0.2126R + 0.7152G + 0.0722B
R, G, B は線形化されたsRGB値

sRGB値(0-1の範囲)を線形値に変換する処理:

function sRGBtoLinear(value: number): number {
  // value は 0-1 の範囲
  return value <= 0.03928
    ? value / 12.92
    : Math.pow((value + 0.055) / 1.055, 2.4);
}

コントラスト比の計算

2つの色の相対輝度から、コントラスト比を算出します。

(L1 + 0.05) / (L2 + 0.05)
L1 = 明るい方の輝度、L2 = 暗い方の輝度
function getContrastRatio(fg: RGBColor, bg: RGBColor): number {
  const l1 = getRelativeLuminance(fg);
  const l2 = getRelativeLuminance(bg);
  const lighter = Math.max(l1, l2);
  const darker = Math.min(l1, l2);
  return (lighter + 0.05) / (darker + 0.05);
}

判定基準

WCAGレベル 通常テキスト 大きいテキスト
AA 4.5:1 以上 3:1 以上
AAA 7:1 以上 4.5:1 以上

背景色の検出

テキストノードの背景色は、親要素を再帰的に辿って最初に見つかったソリッドフィルを使用します。

function findBackgroundColor(node: SceneNode): RGBColor | null {
  let current = node.parent;

  while (current) {
    if ('fills' in current) {
      const solidFill = fills.find(
        fill => fill.type === 'SOLID' && fill.visible !== false
      );
      if (solidFill) return solidFill.color;
    }
    current = current.parent;
  }

  // デフォルトは白
  return { r: 1, g: 1, b: 1 };
}
代替テキスト(Alt)チェック
WCAG 2.1 Success Criterion 1.1.1

スクリーンリーダーを使用する視覚障害のあるユーザーに、画像の内容を伝えるための代替テキストが設定されているかを検証します。Altテキストがないと、画像が何を表しているのか全く伝わりません。

画像ノードの検出

Figmaでは、画像はノードのfillsプロパティ内にIMAGEタイプとして格納されます。

const imageNodes = targetNodes.filter((node) => {
  if ('fills' in node) {
    const fills = node.fills;
    if (Array.isArray(fills)) {
      return fills.some((fill) => fill.type === 'IMAGE');
    }
  }
  return false;
});

判定ロジック

各画像ノードに対して、pluginDataを確認します。

const ALT_TEXT_KEY = 'a11y_alt_text';
const DECORATIVE_KEY = 'a11y_is_decorative';

for (const imageNode of imageNodes) {
  const altText = imageNode.getPluginData(ALT_TEXT_KEY);
  const isDecorative = imageNode.getPluginData(DECORATIVE_KEY);

  // Altテキストがなく、装飾画像でもない場合は警告
  if (!altText && isDecorative !== 'true') {
    issues.push({
      issueType: 'missingAlt',
      severity: 'warning'
    });
  }
}

装飾目的の画像はisDecorative: trueを設定することで、警告をスキップできます。

タッチターゲットサイズ
WCAG 2.1 Success Criterion 2.5.5

運動機能に障害のあるユーザーや、指が太いユーザー、揺れる電車内でスマートフォンを操作するユーザーでも正確にタップできるよう、ボタンやリンクのサイズを検証します。小さすぎるターゲットは誤タップの原因になります。

最小サイズ基準

タッチ操作を行うインタラクティブ要素は、最小48×48pxのサイズが推奨されます。

width >= 48px && height >= 48px
WCAG 2.1 Level AAA / iOS・Android ガイドライン準拠

インタラクティブ要素の検出

ノード名に特定のキーワードが含まれるか、コンポーネントインスタンスかどうかで判定します。

const interactiveElements = targetNodes.filter((node) => {
  const name = node.name.toLowerCase();
  return (
    name.includes('button') ||
    name.includes('btn') ||
    name.includes('link') ||
    name.includes('input') ||
    name.includes('checkbox') ||
    name.includes('radio') ||
    name.includes('select') ||
    name.includes('tab') ||
    node.type === 'INSTANCE'
  );
});

サイズ判定

const MIN_TOUCH_TARGET = 48;

if (element.width < MIN_TOUCH_TARGET || element.height < MIN_TOUCH_TARGET) {
  issues.push({
    issueType: 'smallTouchTarget',
    severity: 'warning',
    message: `${element.width}×${element.height}px(推奨: 48×48px)`
  });
}
フォーカス順序
WCAG 2.1 Success Criterion 2.4.3

キーボードのみで操作するユーザーが、Tabキーで要素間を移動する際の順序を検証します。視覚的なレイアウトと操作順序が一致しないと、ユーザーは混乱し、目的の要素にたどり着けなくなります。

フォーカス順序とは

キーボードユーザーがTabキーで要素間を移動する際の順序です。論理的で予測可能な順序が重要です。

判定ロジック

インタラクティブ要素にpluginDataでフォーカス順序が設定されているかを確認します。

const FOCUS_ORDER_KEY = 'a11y_focus_order';

for (const element of interactiveElements) {
  const focusOrder = element.getPluginData(FOCUS_ORDER_KEY);

  if (!focusOrder) {
    issues.push({
      issueType: 'noFocusOrder',
      severity: 'info',
      message: 'フォーカス順序が設定されていません'
    });
  }
}

フォーカス順序は「情報」レベルの問題として報告されます。視覚的な順序と一致していれば、明示的な設定は必須ではありません。

参考資料

FigmaNaviの判定基準は以下のガイドラインに基づいています。