Nuxt UI Validation

Validates Nuxt UI component usage and enforces v4 migration patterns.

Overview

This rule helps migrate from Nuxt UI v3 to v4 by:

  • Catching deprecated component names
  • Validating semantic color props
  • Enforcing v4 naming conventions
  • Checking model modifier patterns

Source Code

import { defineCodeRule } from "@syncrolabs/claude-code-validator";
import type { PatternRule } from "@syncrolabs/claude-code-validator";

const patterns: PatternRule[] = [
  {
    regex: /UFormGroup/,
    message: "❌ UFormGroup is outdated. Use UFormField instead.",
    replacement: "UFormField",
  },
  {
    regex: /UButtonGroup/,
    message: "❌ UButtonGroup has been renamed in v4. Use UFieldGroup instead.",
    replacement: "UFieldGroup",
  },
  {
    regex: /UPageMarquee/,
    message: "❌ UPageMarquee has been renamed in v4. Use UMarquee instead.",
    replacement: "UMarquee",
  },
  {
    regex: /UPageAccordion/,
    message:
      "❌ UPageAccordion is deprecated in v4. Use UAccordion from @nuxt/ui-pro instead.",
    replacement: "UAccordion",
  },
  {
    regex: /v-model[.:]\w*nullify/,
    message:
      '❌ The "nullify" model modifier has been renamed to "nullable" in Nuxt UI v4.',
    replacement: "v-model:nullable",
  },
];

const COMPONENTS_WITH_COLOR = [
  "UButton",
  "UBadge",
  "UChip",
  "UAlert",
  "UToast",
  "UInput",
  "UTextarea",
  "USelect",
  "USelectMenu",
  "UInputMenu",
  "UPinInput",
  "UInputNumber",
  "UCheckbox",
  "UCheckboxGroup",
  "URadioGroup",
  "USwitch",
  "UBanner",
  "UCalendar",
  "UTabs",
  "UNavigationMenu",
  "UContentNavigation",
  "UContentToc",
  "UPagination",
  "UProgress",
  "USlider",
  "UDivider",
];

const SEMANTIC_COLORS = ["primary", "neutral", "error", "success", "warning"];

const COLOR_MAPPING: Record<string, string> = {
  red: "error",
  green: "success",
  yellow: "warning",
  gray: "neutral",
  grey: "neutral",
  blue: "primary",
};

function validateColors(content: string, filePath: string): string[] {
  const errors: string[] = [];

  for (const component of COMPONENTS_WITH_COLOR) {
    const regex = new RegExp(`<${component}[^>]*:?color=["']([^"']+)["']`, "g");
    let match;

    while ((match = regex.exec(content)) !== null) {
      const color = match[1];

      if (!SEMANTIC_COLORS.includes(color)) {
        const suggested = COLOR_MAPPING[color] || "primary";
        errors.push(
          `❌ Invalid color "${color}" on <${component}>\n` +
            `   → Use Nuxt UI v4 semantic color: "${suggested}"\n` +
            `   ℹ️  Valid colors: ${SEMANTIC_COLORS.join(", ")}\n` +
            `   📄 File: ${filePath}`
        );
      }
    }
  }

  return errors;
}

export const nuxtUIRules = defineCodeRule({
  name: "nuxt-ui",
  description: "Validate Nuxt UI component usage and patterns",

  shouldRun: (context) => {
    return context.filePath.endsWith(".vue");
  },

  validate(context) {
    const errors: string[] = [];

    // Check pattern-based rules
    for (const { regex, message, replacement } of patterns) {
      if (regex.test(context.content)) {
        let error = message;
        if (replacement) {
          error += `\n   → Use: ${replacement}`;
        }
        error += `\n   📄 File: ${context.filePath}`;
        errors.push(error);
      }
    }

    // Check color prop validation
    const colorErrors = validateColors(context.content, context.filePath);
    errors.push(...colorErrors);

    return errors;
  },
});

What It Catches

Deprecated Components

<!-- ❌ Blocked -->
<UFormGroup label="Name">
  <UInput v-model="name" />
</UFormGroup>

<!-- ✅ Allowed -->
<UFormField label="Name">
  <UInput v-model="name" />
</UFormField>

Invalid Colors

<!-- ❌ Blocked -->
<UButton color="red">Delete</UButton>

<!-- ✅ Allowed -->
<UButton color="error">Delete</UButton>

Renamed Components

<!-- ❌ Blocked -->
<UButtonGroup>
  <UButton>One</UButton>
  <UButton>Two</UButton>
</UButtonGroup>

<!-- ✅ Allowed -->
<UFieldGroup>
  <UButton>One</UButton>
  <UButton>Two</UButton>
</UFieldGroup>

Model Modifiers

<!-- ❌ Blocked -->
<UInput v-model:nullify="value" />

<!-- ✅ Allowed -->
<UInput v-model:nullable="value" />

Test Cases

The rule includes comprehensive test coverage:

describe("Nuxt UI Rules", () => {
  it("should detect UFormGroup", () => {
    const context = {
      toolName: "Write",
      filePath: "test.vue",
      content: '<UFormGroup label="Test">',
      operation: "write" as const,
    };

    const errors = nuxtUIRules.validate(context);
    expect(errors).toHaveLength(1);
    expect(errors[0]).toContain("UFormGroup");
  });

  it("should detect invalid color", () => {
    const context = {
      toolName: "Write",
      filePath: "test.vue",
      content: '<UButton color="red">Click</UButton>',
      operation: "write" as const,
    };

    const errors = nuxtUIRules.validate(context);
    expect(errors).toHaveLength(1);
    expect(errors[0]).toContain("red");
  });
});

Benefits

  • Prevents outdated patterns - Catches v3 patterns during migration
  • Enforces semantic colors - Uses meaningful color names
  • Clear error messages - Provides actionable suggestions
  • Comprehensive - Covers all 23 components with color props