ANTIGRAVITY LABEN
記事一覧/AIツール
AIツール/2026-03-27中級

AI生成UIに違和感を覚える理由 — 表面的なAI臭さの奥にある「判断の不在」という本質

AIが生成したUIに「なんか違う」と感じる原因は、グラデーションや絵文字の過剰使用だけではありません。本質的な問題は「判断の不在」にあります。型定義、デザイントークン、条件分岐に意図を宿す方法を解説します。

ai-tools15ui-design2design-judgmenttailwind2typescript27vibe-coding5

AIが生成したUIを眺めていると、どうしても「なんか違う」という違和感を感じることがあります。

グラデーション、絵文字、過度な角丸、影の乱用……確かにこうした表面的な要素は目につきます。しかし、本当の問題はそこではありません。AIが生成したUIが「つくられ感」を漂わせる根本原因は、判断の不在 にあるのです。

このブログでも繰り返し語ってきた「バイブコーディング」の本質は、AIに判断を注入する という営為です。今回の記事では、その判断がUIコンポーネントの実装レベルでどのように表現されるべきかについて、具体的に解説します。

AI生成UIが「違う」と感じられる表面的な理由

まずは、目に映る「らしさ」の正体から始めましょう。

グラデーションの「安定性」

Tailwind CSSでよく見かける from-indigo-500 to-purple-600 のようなグラデーション。これは「統計的に失敗しにくい配色」です。AIモデルは大量の成功したデザインから学んでいるため、視覚的な「はずれ」が少ない配色を選びます。

ただし、そこに 意図がない のです。なぜこの色なのか。ブランドカラーはもっと濃いかもしれません。むしろモノクロメイン、アクセントだけ色を付けるべきかもしれません。

絵文字の過剰使用

🚀 🎉 ✨ 💡 🔥

AIは「親しみやすさ」を数字で最大化しようとするため、こうした絵文字を多用します。しかし、絵文字の出現箇所や数は、「対象ユーザーがどう感じるか」という人間の判断 を必要とします。

角丸とカード

rounded-2xlrounded-full の多用も特徴的です。モダンで親しみやすい見た目——これもAIが「失敗しにくい選択」として学習したものです。

でも、本当にボタンは rounded-lg より rounded-2xl の方が良いのでしょうか。親指で押すタッチターゲットとして最適化するなら、角丸は目立たず、むしろ 44px以上の押下領域 が重要です。

本質: UIに宿る「判断」の有無

ここからが核心です。

良いUIデザインと、AI生成UIの本質的な違いは何か。それは 人間の判断 です。

CTAボタンが他の要素より目立つのは、「ユーザーに最も重要なアクションを取ってもらいたい」という判断があるから。

エラーボタンの角丸を小さくするのは、「危険な操作だから、よく見て判断させたい」という心配りがあるから。

モーダルの背景を濃くするのは、「背景のコンテンツに気を取られず、モーダル内容だけに集中させたい」という設計判断があるから。

AIはこうした 「なぜ」の積み重ね を持っていません。統計的に「良い」とされた要素の組み合わせを選ぶだけです。

判断をコードに宿す3つの方法

では、どうすればUIに判断を注入できるのでしょうか。三つの実践的なアプローチを紹介します。

1. 型で判断を表現する

まず、単純なButtonコンポーネントを考えてみます。

// AI臭い実装: 色を直接指定
interface ButtonProps {
  color?: string; // 'blue' | 'red' | 'green' ...
  children: React.ReactNode;
}
 
export function Button({ color = 'blue', children }: ButtonProps) {
  return (
    <button className={`bg-${color}-500 text-white px-4 py-2 rounded-lg`}>
      {children}
    </button>
  );
}

この書き方には判断がありません。呼び出し側で <Button color="red">削除</Button> とすれば、赤いボタンができますが、「なぜ赤いのか」という意図が不透明です。

では、意図を型で表現してみましょう。

// 判断を型に宿した実装
type ButtonVariant = 'primary' | 'secondary' | 'destructive' | 'ghost' | 'link';
type ButtonSize = 'sm' | 'md' | 'lg';
 
interface ButtonProps {
  variant?: ButtonVariant; // 意味のある選択肢のみ
  size?: ButtonSize;
  isLoading?: boolean;
  disabled?: boolean;
  children: React.ReactNode;
}
 
export function Button({
  variant = 'primary',
  size = 'md',
  isLoading = false,
  disabled = false,
  children
}: ButtonProps) {
  const baseStyles = 'font-medium transition-all duration-200 focus:ring-2';
 
  const variantStyles = {
    primary: 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-300',
    secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300 focus:ring-gray-400',
    destructive: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-300',
    ghost: 'bg-transparent text-gray-900 hover:bg-gray-100 focus:ring-gray-300',
    link: 'bg-transparent text-blue-600 underline hover:no-underline focus:ring-blue-300',
  };
 
  const sizeStyles = {
    sm: 'px-3 py-1.5 text-sm',
    md: 'px-4 py-2 text-base min-h-[44px]', // 高齢者対応タッチターゲット
    lg: 'px-6 py-3 text-lg min-h-[48px]',
  };
 
  return (
    <button
      className={`${baseStyles} ${variantStyles[variant]} ${sizeStyles[size]} ${
        isLoading ? 'opacity-70 cursor-wait pointer-events-none' : ''
      } ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`}
      disabled={disabled || isLoading}
    >
      {isLoading ? '処理中...' : children}
    </button>
  );
}

ここで何が変わったのか:

  • variant で意図を表現: 「これは主要アクション」「これは危険な操作」という意図が型として現れます
  • size に44px最小化を組み込み: アクセシビリティへの判断をコードに宿します
  • isLoading で状態を明示化: 「処理中」という状態に対して「待機カーソル + 不透明化 + クリック無効化」という判断を一貫させます

2. デザイントークンにブランド判断を込める

次に、Tailwindのカスタムトークンレベルで判断を示す例です。

// tailwind.config.ts - デザイントークンに判断を宿す
export default {
  theme: {
    colors: {
      // ブランドパレット: Antigravity の世界観を表現
      surface: 'oklch(0.98 0.002 0)',      // 真白ではなく、わずかに温い白
      'surface-alt': 'oklch(0.94 0.002 0)', // サブ背景
 
      // セマンティックカラー: 「役割」に基づいた色
      primary: 'oklch(0.55 0.2 260)',      // ブランドプライマリ(青紫)
      secondary: 'oklch(0.65 0.1 260)',    // セカンダリ(淡い青紫)
 
      success: 'oklch(0.58 0.15 142)',     // 成功(緑)
      warning: 'oklch(0.68 0.15 65)',      // 警告(黄)
      error: 'oklch(0.55 0.2 25)',         // エラー(赤)
      info: 'oklch(0.60 0.15 250)',        // 情報(青)
    },
    spacing: {
      // 判断的な余白スケール: フィボナッチ比率ベース
      'xs': '0.25rem',   // 4px - テキスト内余白
      'sm': '0.5rem',    // 8px - 密集要素間
      'md': '1rem',      // 16px - 標準間隔
      'lg': '1.5rem',    // 24px - セクション間隔
      'xl': '2.5rem',    // 40px - メジャーセクション
      'xxl': '4rem',     // 64px - ページ余白
    },
    fontSize: {
      // タイポグラフィ: 読みやすさへの判断
      'xs': ['0.75rem', { lineHeight: '1.5' }],   // 12px / 1.5
      'sm': ['0.875rem', { lineHeight: '1.5' }],  // 14px / 1.5
      'base': ['1rem', { lineHeight: '1.6' }],    // 16px / 1.6
      'lg': ['1.125rem', { lineHeight: '1.6' }],  // 18px / 1.6
      'xl': ['1.375rem', { lineHeight: '1.5' }],  // 22px / 1.5
      'h2': ['1.75rem', { lineHeight: '1.3' }],   // 28px / 1.3
      'h1': ['2.25rem', { lineHeight: '1.2' }],   // 36px / 1.2
    },
  },
};

ここで評価すべき点:

  • oklch 色空間: RGB や HSL より人間の知覚に近い色定義。ブランド感度が高まります
  • セマンティックな色名: primary error success — 色の「役割」が明確です
  • line-height の意図的設定: 見出しは行間を詰める(1.2)、本文は広げる(1.6)という読みやすさへの判断

3. 条件分岐に「なぜ」を書く

最後に、コンポーネント内の条件分岐そのものが判断の表現だという例です。

// FormInput.tsx - アクセシビリティへの判断が条件分岐に宿る
interface FormInputProps {
  label: string;
  value: string;
  onChange: (val: string) => void;
  error?: string;
  isLoading?: boolean;
  required?: boolean;
}
 
export function FormInput({
  label,
  value,
  onChange,
  error,
  isLoading,
  required,
}: FormInputProps) {
  const inputId = `input-${label.replace(/\s/g, '-')}`;
 
  return (
    <div className="flex flex-col gap-md">
      <label htmlFor={inputId} className="font-medium text-gray-900">
        {label}
        {required && <span className="text-error ml-1">*</span>}
      </label>
 
      <input
        id={inputId}
        type="text"
        value={value}
        onChange={(e) => onChange(e.target.value)}
        disabled={isLoading}
        // エラー状態への判断:
        // 1. 視覚的に区別(赤い枠)
        // 2. スクリーンリーダー利用者に伝達(aria-invalid)
        // 3. aria-describedby でエラーメッセージを関連付ける
        aria-invalid={!!error}
        aria-describedby={error ? `${inputId}-error` : undefined}
        className={`
          px-3 py-2 border-2 rounded-md transition-colors
          ${error
            ? 'border-error bg-error/5 focus:ring-error/30'
            : 'border-gray-300 focus:ring-primary/30'
          }
          ${isLoading ? 'cursor-wait opacity-70' : ''}
          focus:outline-none focus:ring-2
        `}
      />
 
      {/* エラー表示: role="alert" で即座に読み上げさせる */}
      {error && (
        <div
          id={`${inputId}-error`}
          role="alert"
          aria-live="polite"
          className="text-error text-sm font-medium"
        >
          {error}
        </div>
      )}
    </div>
  );
}

条件分岐に込められた判断:

  • aria-invalid + aria-describedby: スクリーンリーダー利用者にエラーを正確に伝えたい
  • role="alert" + aria-live="polite": エラー表示を「割り込み情報」として即座に読み上げさせたい
  • cursor-wait: 「処理中です、お待ちください」という心理的安心感を与えたい

なぜこうした判断が必要か

ここまでの3つのアプローチの共通点は、すべての選択に「なぜ」がある という点です。

  • ボタンのバリアントが「primary」「destructive」「ghost」に限定されるのは、ユーザー心理と一貫性を考えたから
  • 最小高さが44pxなのは、高齢者や指の大きな人でも操作できるようにしたから
  • エラーメッセージに aria-live を付けるのは、全盲者にも同じリアルタイム情報を与えたいから

AIが「違う」ように見えるのは、こうした 各所に宿る意図の連鎖 が欠けているからです。逆に言えば、この「なぜ」を丁寧に組み込むだけで、生成されたコードもグッと人間らしくなるのです。

バイブコーディングでの活用

Antigravity Lab が推奨する「バイブコーディング」は、まさにこうした判断をAIに 注入する プロセスです。

# プロンプト例: デザイントークンを先に渡す
 
上記の tailwind.config.ts を参照してください。
 
以下の要件で Button コンポーネントを実装してください:
- variant: 'primary' | 'secondary' | 'destructive' | 'ghost' | 'link'
- size: 'sm' | 'md' | 'lg' (最小タッチターゲット 44px)
- isLoading 状態では cursor-wait + opacity-70 + disabled
- role, aria-* 属性で支援技術対応
- デザイントークンの色・余白・タイポグラフィのみ使用
 
実装後、「なぜこの条件分岐を選んだか」をコメントで説明してください。

こうしてAIに「判断の根拠」を明示してから生成させると、AI出力の質は劇的に変わります。

シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

AIツール2026-03-27
AI生成UIを超えるデザイン実装 — 型・トークン・条件分岐に判断を宿す実践テクニック
AI生成UIの「判断の不在」を克服するための実践的なデザイン実装テクニック集。TypeScriptの型システムによるUI意図の表現、デザイントークンの設計、条件分岐のセマンティック化、コンポーネントAPIの設計パターンまで、コード例とともに体系的に解説します。
AIツール2026-07-14
ローカル Gemma とクラウド Gemini 3.5 Flash を「検証しやすさ」で振り分ける設計
ローカルとクラウドの振り分けを「機密かどうか」「速いかどうか」で決めていた頃、判断が毎回ぶれていました。本当に効いた軸は、出力が間違っていたときに安く気づけて安く戻せるか、でした。検証しやすさと復旧コストからモデルを選ぶルーターを、判定コードと実測ログの設計まで含めて組み立てます。
AIツール2026-06-24
夜通し回したローカルエージェントが、明け方には一手ごとに数秒待たされる — Ollama の応答時間を文脈長から逆算して一定に保つ
ローカルで長時間ループするエージェントの一手が後半ほど重くなる原因を、Ollama の所要時間フィールドから計測し、num_ctx の固定とローリング要約で遅延を平準化する実装メモです。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →