UI consistency guardrails for shadcn/ui, Radix UI, and Tailwind CSS projects.
中文说明 · GitHub Pages · Rules Reference
ESLint plugin for enforcing UI consistency in shadcn/ui + Radix UI + Tailwind CSS projects.
This plugin helps maintain design system consistency by enforcing rules around component usage, styling patterns, and design token adherence. It's specifically designed for projects using shadcn/ui, Radix UI primitives, and Tailwind CSS.
pnpm add -D @aicode-nexus/eslint-plugin-ui-consistencyThe easiest way to get started is using one of the preset configurations:
Recommended preset (balanced approach):
// eslint.config.js
import { recommended } from "@aicode-nexus/eslint-plugin-ui-consistency/presets";
export default [
recommended,
// ... your other config
];Strict preset (all rules enabled):
// eslint.config.js
import { strict } from "@aicode-nexus/eslint-plugin-ui-consistency/presets";
export default [
strict,
// ... your other config
];For custom rule configuration:
// eslint.config.js
import { uiConsistencyPlugin } from "@aicode-nexus/eslint-plugin-ui-consistency";
export default [
{
plugins: {
"ui-consistency": uiConsistencyPlugin,
},
rules: {
"ui-consistency/no-raw-interactive-elements": "error",
"ui-consistency/no-raw-palette-utility": "error",
// ... configure other rules as needed
},
},
];This plugin provides 11 rules to enforce UI consistency:
Disallows raw HTML interactive elements (button, input, textarea, select, a, label, form, fieldset) in favor of UI library primitives.
Why: Ensures consistent styling, accessibility, and behavior across all interactive elements.
// ❌ Bad
<button onClick={handleClick}>Click me</button>
// ✅ Good
<Button onClick={handleClick}>Click me</Button>Disallows className overrides on governed UI components to maintain design system integrity.
Why: Prevents style drift by ensuring components use their built-in styling patterns.
Restricts shell-only components (like SidebarFooter, SidebarGroup) to layouts and patterns only.
Why: Prevents misuse of layout-specific components in business logic pages.
Disallows raw Tailwind color palette utilities (e.g., bg-blue-500, text-red-600) in favor of design tokens.
Why: Enforces semantic color usage and ensures consistent theming.
// ❌ Bad
<div className="bg-blue-500 text-white">Content</div>
// ✅ Good
<div className="bg-primary text-primary-foreground">Content</div>Prevents importing from legacy or internal UI paths that bypass official exports.
Why: Ensures stable imports and prevents breaking changes from internal refactoring.
Disallows unnecessary className on icons inside Button components.
Why: Button components already handle icon sizing and spacing through [&_svg] styles.
// ❌ Bad
<Button>
<Icon className="h-4 w-4 mr-2" />
Click me
</Button>
// ✅ Good
<Button>
<Icon />
Click me
</Button>Disallows arbitrary Tailwind utilities (e.g., bg-[#ff0000], text-[14px]) that bypass design tokens.
Why: Maintains design system consistency and prevents one-off values.
// ❌ Bad
<div className="bg-[#3b82f6] text-[14px]">Content</div>
// ✅ Good
<div className="bg-primary text-sm">Content</div>Disallows arbitrary z-index values in favor of predefined layers.
Why: Prevents z-index conflicts and maintains a clear stacking context hierarchy.
// ❌ Bad
<div className="z-[9999]">Modal</div>
// ✅ Good
<div className="z-50">Modal</div>Disallows dark mode implementations using raw color palette utilities.
Why: Design tokens handle dark mode automatically through CSS variables.
// ❌ Bad
<div className="bg-white dark:bg-gray-900">Content</div>
// ✅ Good
<div className="bg-background">Content</div>Disallows arbitrary spacing values that bypass the design system's spacing scale.
Why: Maintains consistent spacing throughout the application.
// ❌ Bad
<div className="p-[13px] m-[7px]">Content</div>
// ✅ Good
<div className="p-3 m-2">Content</div>Enforces importing composition components from the correct path.
Why: Maintains clear separation between primitive and composition components.
// ❌ Bad
import { DataTable } from "@your-org/ui/components/data-table";
// ✅ Good
import { DataTable } from "@your-org/ui/compositions/";Balanced configuration suitable for most projects:
- ✅
no-raw-interactive-elements(error) - ✅
no-raw-palette-utility(error) ⚠️ no-arbitrary-utility(warn)- ✅
no-button-icon-classname(error) ⚠️ no-hardcoded-z-index(warn)
All rules enabled for maximum consistency:
- All rules from recommended
- ✅
no-primitive-classname(error) - ✅
no-shell-only-component-usage(error) - ✅
no-forbidden-ui-import-path(error) - ✅
no-arbitrary-utility(error - upgraded from warn) - ✅
no-hardcoded-z-index(error - upgraded from warn) ⚠️ no-dark-mode-hardcode(warn)⚠️ no-inconsistent-spacing(warn)- ✅
prefer-composition-import(error)
Start with warnings and gradually increase strictness:
import { uiConsistencyPlugin } from "@aicode-nexus/eslint-plugin-ui-consistency";
export default [
{
plugins: {
"ui-consistency": uiConsistencyPlugin,
},
rules: {
"ui-consistency/no-raw-interactive-elements": "warn",
"ui-consistency/no-raw-palette-utility": "warn",
"ui-consistency/no-arbitrary-utility": "warn",
},
},
];Apply stricter rules to new code:
import { recommended, strict } from "@aicode-nexus/eslint-plugin-ui-consistency/presets";
export default [
recommended, // Default for all files
{
files: ["src/features/new-dashboard/**/*.tsx"],
...strict, // Stricter rules for new features
},
];Contributions are welcome! Please feel free to submit issues or pull requests.
MIT
- shadcn/ui - Re-usable components built with Radix UI and Tailwind CSS
- Radix UI - Unstyled, accessible components
- Tailwind CSS - Utility-first CSS framework