Custom Rules Quickstart
Get Started with your Own ViewLint Rules
Do I Need This Rule?
Before you start creating a Custom Rule, first ask yourself whether or not this rule should be developed with ViewLint.
ViewLint rules should ideally meet a few criteria:
- Cannot be Checked via Code Only: If your rule can be checked just by looking at the code, it's probably better off in a normal linter like ESLint.
- Applicable Across Pages and Browser Configs: If your rule doesn't apply to all pages of your website, and across mobile/desktop, it's probably better as a Unit Test or normal Playwright test.
However, these are not strict rules, and ViewLint is incredibly flexible and adaptable that you should be able to use it with any setup you need.
Creating Your First Rule
This page walks you through creating a tiny ViewLint plugin with one rule.
The goal is intentionally simple: report any <marquee> tag. You can swap this out for your own design-system checks later.
Technically this violates the condition that the rule should not be able to be checked via code only, but this is just a simple example to show the rule syntax.
This tutorial focuses on the core loop:
- Define a rule
- Put it into a plugin
- Load the plugin in
viewlint.config.ts - Run ViewLint
Create a rule
Create no-marquee.ts:
import { defineRule } from "viewlint/plugin";
export default defineRule({
meta: {
severity: "warn",
docs: {
description: "Disallow <marquee> elements",
},
},
async run({ evaluate }) {
await evaluate(({ report, scope }) => {
for (const el of scope.queryAll("marquee")) {
if (!(el instanceof HTMLElement)) continue;
report({
message: "Avoid <marquee>. Use modern CSS animations instead.",
element: el,
});
}
});
},
});Let's see what is happening:
- We use the
defineRulehelper to give us type hints. It's optional, but very useful if you're working in TypeScript - We include a
metafield with a default severity and some docs as description - The
runfunction is what actually executes the rule, and reports issues - We use the
evaluatefunction passed fromrun, which allows us to switch into the Browser environment - We query for marquee elements and run
report(passed fromevaluateinto Browser environment) on each one- Report takes an
elementand amessage
- Report takes an
Create a plugin
Create viewlint-plugin-example.ts:
import noMarquee from "./no-marquee";
const plugin = {
meta: {
name: "viewlint-plugin-example",
},
rules: {
"no-marquee": noMarquee,
},
};
export default plugin;Load the plugin in your config
In viewlint.config.ts:
import { defineConfig } from "viewlint/config";
import example from "./viewlint-plugin-example";
export default defineConfig({
plugins: {
example,
},
rules: {
"example/no-marquee": "warn",
},
});Run ViewLint
This will now run your rule! Run with --verbose to validate that your new rule really is running.
Try on a website with marquee to see the error throw.
npx viewlint https://example.comSee Custom Rules Reference for more information.