Skip to content
Brok UI

Loading…

No results

Lint UI-408

no-restyle

Classes on a design-system component are limited to what the policy and the component's contract allow; appearance comes from variants.

UI-408 default: warning api CONFLICTING_RECIPE_OVERRIDE RESTYLE

Two layers

The registry contract is always on: a class in a utility family the component's recipe owns (the button's padding and height belong to size) is reported with the prop that owns it, and the values that prop accepts. The important form (!px-8) is the sanctioned escape hatch.

The project policy turns on once you set allow, deny or contracts. Then every class on a design-system component is checked: appearance comes from variants, and layout goes where the policy says. Start with layout allowed:

ui.json JSON
"no-restyle": ["error", { "allow": ["layout"] }]
orders/edit.blade.php Blade
{{-- Allowed: layout goes on the component, appearance comes from props. --}}
<brok:button class="mt-4 w-full">Save changes</brok:button>
<brok:button size="lg" variant="destructive">Delete account</brok:button>

{{-- Reported: padding, colour and shape are the recipe's to own. --}}
<brok:button class="p-4 bg-pink-500 rounded-full">Save changes</brok:button>

Spacing findings list the component's sizes; other appearance findings list its variants. The linter reads both from the installed contract.

terminal
"p-4" is not allowed on <button>: <button> owns its spacing. Use a size (sm, md, lg, icon),
or margin on it, gap on the parent element, or padding on the container for space around it.

Contracts

Give components different rules. A contract replaces the keys it writes and inherits the rest; include layout in a contract's allow when it should keep layout classes. With deny alone, every class except the denied ones is allowed.

ui.json JSON
"no-restyle": ["error", {
  "allow": ["layout"],
  "deny": ["w-*"],
  "contracts": [
    { "pattern": "^card\\.title$", "allow": ["layout", "typography"] },
    { "pattern": "^card\\.content$", "allow": ["layout", "spacing"] }
  ]
}]
orders/edit.blade.php Blade
{{-- Allowed by the contracts. --}}
<brok:card.title class="text-sm">Account settings</brok:card.title>
<brok:card.content class="p-4">Profile details</brok:card.content>

{{-- Reported: colour is not opened by either contract; width is denied everywhere. --}}
<brok:card.title class="text-pink-500">Account settings</brok:card.title>
<brok:button class="w-full">Save</brok:button>

Your own words

One message for every finding, or one per category (layout, color, typography, spacing, shape, effects, motion) with a default. sizes, variants, component, category, entries, around and file are available.

ui.json JSON
"no-restyle": ["error", {
  "allow": ["layout"],
  "message": {
    "spacing": "Use a {{component}} size: {{sizes|none defined}}.",
    "default": "Use a {{component}} variant: {{variants|none defined}}."
  }
}]

Limits

  • Only installed design-system components are checked. Plain elements and unknown components are outside this rule.
  • A class the grammar cannot classify is reported as unclassified, even with layout allowed. Allow a custom class by name.
  • Entries match class names and groups, not equivalent CSS effects: w-* does not cover [width:100%].
  • Allowing p-* also allows p-[13px] through this rule; no-arbitrary-values still checks the value.
  • Class values the linter cannot read are require-static-classes findings.

Configure

In the lint.rules block of ui.json. It accepts allow, deny, contracts, message, include, exclude; see the policy for what the entries match.

ui.json JSON
"no-restyle": ["warning", { "allow": [...], "deny": [...], "contracts": [], "message": "...", "include": ["resources/views/components/ui"], "exclude": ["resources/views/legacy/**"] }]
terminal
php artisan ui:check --explain no-restyle

Suppress one line

blade
{{-- ui-lint-disable-next-line no-restyle: reason --}}