Agent Wranglers

Styles and components Required

Build each control once

Build each control once in a shared component library, and use it on every screen. Never style a raw button, input, select or table on a page.

How to do it

Put buttons, fields, selects, tables, dialogs, toasts and empty states in one folder or package. Base them on native elements, as native controls explains. Add a lint rule that bans the raw elements outside that folder. For server-rendered apps, do the same with template macros or partials.

Example

// eslint.config.js
import tseslint from 'typescript-eslint';

export default [{
  files: ['src/screens/**/*.tsx'],
  languageOptions: { parser: tseslint.parser },
  rules: {
    'no-restricted-syntax': ['error', {
      selector: "JSXOpeningElement[name.name=/^(button|input|select|table|dialog)$/]",
      message: 'Use the component from src/ui instead of a raw element.',
    }],
  },
}];
import pathlib
import re
import sys

RAW_CONTROL = re.compile(r"<(button|input|select|table|dialog)\b")
problems = [
    str(path) for path in pathlib.Path("templates/screens").rglob("*.html")
    if RAW_CONTROL.search(path.read_text())
]
print("\n".join(problems) or "every control comes from templates/ui")
sys.exit(1 if problems else 0)

How to check

The linter fails when a screen outside the library uses <button>, <input>, <select> or <table> directly.

For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers app interface playbook: https://agentwranglers.se/playbook/app-ui/styles-and-components/shared-components.