Cory Rylan

My name is , Software Developer and Speaker. Building Design Systems and Web Components.

Follow @coryrylan
Web Components

Enforcing Design System Rules in AI-Generated UI

By Cory Rylan

- 5 minutes

An AI coding agent can use the right design system components and still use them incorrectly. A form control has no label. An icon name looks right but isn't in the library. A spacing token looks reasonable but doesn't exist.

These are mistakes we can catch before the change reaches code review.

In Your Context Files Are a Code Smell, I wrote about moving enforceable engineering rules into tooling. Let's apply that idea to a small UI built with NVIDIA Elements. We'll add design-system checks to the project, use their feedback to fix the code, and run the same checks in CI.

Start with a broken example

Imagine an agent is adding a model deployment form to an existing Elements application. It produces this HTML in src/deployment.html:

<div class="deployment-panel" nve-layout="column gap:md">
  <nve-input>
    <input type="text" name="model" />
  </nve-input>

  <nve-button>
    <nve-icon name="rocket-launch"></nve-icon>
    Deploy model
  </nve-button>
</div>

Notice the missing label inside nve-input. The button also uses rocket-launch as an icon name, but that name doesn't exist in the Elements icon library. The markup looks reasonable, yet the agent has guessed an API value.

The agent adds a stylesheet in src/deployment.css:

.deployment-panel {
  padding: var(--nve-ref-space-300);
}

That variable looks like a spacing token. It even has the right prefix. However, --nve-ref-space-300 doesn't exist in Elements. Asking the agent to "use design tokens" still leaves room for this mistake.

These snippets focus on component usage. The surrounding application already has Elements imports and styles configured; we're not implementing the form's submission behavior here.

Add the lint rules

The Elements lint package provides ESLint rules for component APIs, composition, and design tokens. Install it with ESLint as a development dependency:

npm install --save-dev eslint @nvidia-elements/lint

This example uses Node.js 24, ESLint 10, and @nvidia-elements/lint 2.7.4. That lint release requires ESLint 10 or later. If your project uses ESLint 9, upgrade ESLint or choose a compatible version of the Elements lint package. Commit the dependency lockfile so local development and CI use the same versions.

Create an eslint.config.mjs file and import the recommended configuration:

import { elementsRecommended } from '@nvidia-elements/lint/eslint';

export default [
  ...elementsRecommended
];

If the project already has an ESLint configuration, add elementsRecommended to that configuration. The preset includes checks for both HTML and CSS. Its HTML and CSS file patterns target src, so adjust them if your source files live elsewhere.

Now run ESLint against our two files:

npx eslint src/deployment.html src/deployment.css

The command fails with three errors. Here's the output shortened to file locations and rule names:

src/deployment.css
  2:3  error  @nvidia-elements/lint/no-unknown-css-variable

src/deployment.html
  2:3   error  @nvidia-elements/lint/no-missing-control-label
  7:15  error  @nvidia-elements/lint/no-unexpected-attribute-value

3 problems (3 errors, 0 warnings)

Each error points to a specific part of the design system's contract:

The agent now has a file, a location, and a rule to investigate. It can look up the relevant API and make a targeted correction.

Fix the component usage

First, add the label using Elements' label composition and replace the guessed icon name with rocketship:

<div class="deployment-panel" nve-layout="column gap:md">
  <nve-input>
    <label>Model name</label>
    <input type="text" name="model" />
  </nve-input>

  <nve-button>
    <nve-icon name="rocketship"></nve-icon>
    Deploy model
  </nve-button>
</div>

The label is now part of the nve-input composition. The button uses rocketship, a name from the Elements icon library.

Next, replace the unknown variable with the medium spacing token:

.deployment-panel {
  padding: var(--nve-ref-space-md);
}

Run the same ESLint command again. These files now pass with no errors or warnings.

This feedback works the same way for a human developer and a coding agent. The rule lives in the project, ready to catch the same mistake in the next change.

Give the agent discovery tools

We can also help the agent find the correct API before writing the code. The Elements MCP server exposes tools for that work:

  • examples_list and examples_get find existing patterns and their markup.
  • api_get looks up component and utility APIs.
  • api_icons_list lists available icon names.
  • api_tokens_list lists available design tokens.
  • api_validate checks HTML and JSON files or supplied content with Elements lint rules.

For our example, the agent can look up nve-input, find its label composition, and select a real icon name and spacing token. Then it can run the project's ESLint command to check both the HTML and the stylesheet against the committed configuration.

API lookup improves the first attempt. Validation checks what the agent actually wrote.

A task prompt can stay focused:

Build the deployment form using NVIDIA Elements. Use the Elements MCP tools to look up unfamiliar APIs and tokens. Run npm run lint:elements after editing, and fix reported violations without disabling rules or excluding the changed files.

The agent can repeat that edit-and-check loop while it works. To enforce the rules before merging, we need the check in CI too.

Require the check in CI

Add a script to the existing scripts object in package.json:

{
  "scripts": {
    "lint:elements": "eslint src --max-warnings=0"
  }
}

The three rules in our example already report errors. The --max-warnings=0 flag also makes warnings fail the command, so use it when the project requires a warning-free result.

In CI, install dependencies from the lockfile and run the script:

npm ci
npm run lint:elements

Make the lint job a required check for merging. If a developer or agent skips validation locally, CI still checks the files before the change can merge.

Leave room for review

A passing lint check means the configured rules found no violations in the files they analyzed. We still need to test form submission, error recovery, and keyboard and screen reader interactions. A valid spacing token can also be the wrong choice for the layout.

Automating the checks we can express precisely gives us more review time for those decisions. Start with one mistake your team keeps correcting in review. Add a rule, test its valid and invalid cases, and run it wherever the code changes. With Elements, the checks in this example are already available to use.

Bluesky Facebook LinkedIn Email
 

Related Posts

StencilJS

Create your first Web Component with Stencil JS

Create reusable UI components that will work in any JavaScript Framework using StencilJS a Web Component framework.

Read Article
StencilJS

Using Stencil Web Components in Angular and the Angular CLI

Learn how to publish a Stencil Web Component and use in an Angular CLI app.

Read Article
Web Components

Introduction To Web Components

A tutorial on building reusable UI components in any JavaScript framework using Web Component APIs.

Read Article