# Linter

Oxlint rules that check how your app uses Tempo Design System.

## Overview

`@tempoxyz/ds/oxlint` is an Oxlint plugin. Its `tds/*` rules report the mistakes that types cannot catch, such as an icon-only [Button](/docs/components/button) without a name, an import from an internal path, or a native control that a component replaces.

## Principles

* **Check What Types Miss:** Token names and props are already typed. The rules cover accessible names, entrypoints, surfaces, and inline values.
* **Errors Block, Warnings Guide:** A rule that finds a broken result, such as a control without a name, is an error. A rule that finds a likely shortcut, such as a native `<button>`, is a warning.
* **One Configuration per Surface:** The `web` and `mobile` configurations enable the TDS rules and the Zyzz rules together, so you do not choose them one by one.

## Set Up the Linter

Spread the configuration for your surface into the `lint` options of Vite Plus:

```ts title="vite.config.ts"
import { web } from '@tempoxyz/ds/oxlint'
import { defineConfig } from 'vite-plus'

export default defineConfig({ lint: web })
```

Use `mobile` in a React Native app. To change a severity, spread the rules and override one:

```ts title="vite.config.ts"
import { web } from '@tempoxyz/ds/oxlint'
import { defineConfig } from 'vite-plus'

export default defineConfig({
  lint: { ...web, rules: { ...web.rules, 'tds/no-raw-values': 'error' } },
})
```

With an `.oxlintrc.json`, register both plugins and enable the rules:

```json title=".oxlintrc.json"
{
  "jsPlugins": [
    { "name": "tds", "specifier": "@tempoxyz/ds/oxlint" },
    { "name": "zyzz", "specifier": "zyzz/oxlint" }
  ],
  "rules": {
    "tds/no-cross-surface": ["error", { "surface": "web" }],
    "tds/no-internal-imports": "error",
    "tds/no-raw-values": "warn",
    "tds/prefer-component": "warn",
    "tds/require-accessible-name": "error"
  }
}
```

## Rules

The `web` and `mobile` configurations enable these rules. Each section lists the setting that enables the rule on its own.

### Import From Public Entrypoints

`tds/no-internal-imports` accepts only the subpaths in the package `exports` map: `@tempoxyz/ds`, `@tempoxyz/ds/icons`, `@tempoxyz/ds/mobile`, `@tempoxyz/ds/mobile/icons`, their SVG and font files, and `@tempoxyz/ds/oxlint`. It checks imports, re-exports, `import()` expressions, and `import()` types. Both configurations report it as an error.

Enable it in the `rules` of your lint configuration:

```json
{ "rules": { "tds/no-internal-imports": "error" } }
```

**Do:** Import components from `@tempoxyz/ds` and icons from `@tempoxyz/ds/icons`.

**Don't:** Import from `src` or `dist`. Internal paths change without a release note.

### Stay on One Surface

`tds/no-cross-surface` reports an import from the surface that your app does not render. Set `surface` to `web` or `mobile`. Without `surface`, the rule reports a file that imports from both surfaces. Both configurations report it as an error.

Enable it in the `rules` of your lint configuration:

```json
{ "rules": { "tds/no-cross-surface": ["error", { "surface": "web" }] } }
```

**Do:** Import every component and icon of a React Native app from the Mobile entrypoints.

**Don't:** Mix surfaces in one app. Each surface owns its variables, and a Web component does not render in React Native.

### Name Controls Without Text

`tds/require-accessible-name` reports an unnamed `shape="icon"` [Button](/docs/components/button) or `Button.Link`, a [Gauge](/docs/components/gauge), or a Mobile `IconButton`. On Web, `aria-label` or `aria-labelledby` names the element. On Mobile, `accessibilityLabel` also names it. An element in a `render` prop can take its name from the element that owns the prop. Both configurations report it as an error.

Enable it in the `rules` of your lint configuration:

```json
{ "rules": { "tds/require-accessible-name": "error" } }
```

**Do:** Name the action in `aria-label`, on the button or on the trigger that renders it.

**Don't:** Leave an icon-only button or a [Gauge](/docs/components/gauge) unnamed. Screen readers announce only its role.

### Prefer Library Components

`tds/prefer-component` warns on `<button>`, `<details>`, `<dialog>`, `<fieldset>`, `<hr>`, `<kbd>`, `<meter>`, `<progress>`, `<select>`, `<table>`, and `<textarea>`. It also warns on an `<input>` whose `type` has a component, such as `checkbox` for [Checkbox](/docs/components/checkbox) or `range` for [Slider](/docs/components/slider). Elements in a `render` prop pass, and so do inputs with no replacement, such as `file`. Only the `web` configuration enables it, as a warning. Pass element names in `allow` to skip them.

Enable it in the `rules` of your lint configuration:

```json
{ "rules": { "tds/prefer-component": ["warn", { "allow": ["table"] }] } }
```

**Do:** Use [Checkbox](/docs/components/checkbox) and [Button](/docs/components/button), which bring Tempo states, focus rings, and motion.

**Don't:** Restyle a native control that the library already ships.

### Style With Tokens

`tds/no-raw-values` warns on a literal color, such as `#000` or `rgb()`, a length in `px`, `rem`, `em`, or `pt`, or any `fontFamily` in a JSX `style` prop. It also warns on a bare number for a length property, such as `width: 160`. A value that reads `var()` passes. Both configurations report it as a warning.

Enable it in the `rules` of your lint configuration:

```json
{ "rules": { "tds/no-raw-values": "warn" } }
```

**Do:** Set colors and spacing with tokens in a `style` definition.

**Don't:** Hardcode colors and lengths in inline styles. They do not follow the scheme or the token scale.

## Zyzz Rules

The `web` and `mobile` configurations also enable the rules of the Zyzz plugin, which check the styles inside `style()` and `variants()`:

* **`zyzz/no-conflicting-props`:** Reports a `className` or `style` prop that overwrites a style spread, as an error.
* **`zyzz/no-unused`:** Warns on a style in a `namespace styles` block that nothing applies.
* **`zyzz/valid-styles`:** Reports an unknown property or token, as an error.

## Web and Mobile

* **Web:** The `web` configuration enables every rule and sets `surface` to `web`.
* **Mobile:** The `mobile` configuration leaves out `tds/prefer-component`, since React Native renders no HTML elements, and sets `surface` to `mobile`.

## Related

[Agents](/docs/foundations/agents)

Covers the rules that coding agents follow and the checks they run.

[Accessibility](/docs/foundations/accessibility)

Covers the accessible names that the linter checks.

[Getting Started](/docs/introduction/getting-started)

Covers the install and the Zyzz setup that the linter builds on.
