Concise rules for building accessible, fast, delightful UIs Use MUST/SHOULD/NEVER to guide decisions
- Keyboard
- MUST: Full keyboard support per WAI-ARIA APG
- MUST: Visible focus rings (
:focus-visible; group with:focus-within) - MUST: Manage focus (trap, move, and return) per APG patterns
- Targets & input
- MUST: Hit target ≥24px (mobile ≥44px) If visual <24px, expand hit area
- MUST: Mobile
<input>font-size ≥16px or set:<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover" />
- NEVER: Disable browser zoom
- MUST:
touch-action: manipulationto prevent double-tap zoom; set-webkit-tap-highlight-colorto match design
- Inputs & forms (behavior)
- MUST: Hydration-safe inputs (no lost focus/value)
- NEVER: Block paste in
<input>/<textarea> - MUST: Loading buttons show spinner and keep original label
- MUST: Enter submits focused text input In
<textarea>, ⌘/Ctrl+Enter submits; Enter adds newline - MUST: Keep submit enabled until request starts; then disable, show spinner, use idempotency key
- MUST: Don’t block typing; accept free text and validate after
- MUST: Allow submitting incomplete forms to surface validation
- MUST: Errors inline next to fields; on submit, focus first error
- MUST:
autocomplete+ meaningfulname; correcttypeandinputmode - SHOULD: Disable spellcheck for emails/codes/usernames
- SHOULD: Placeholders end with ellipsis and show example pattern (eg,
+1 (123) 456-7890,sk-012345…) - MUST: Warn on unsaved changes before navigation
- MUST: Compatible with password managers & 2FA; allow pasting one-time codes
- MUST: Trim values to handle text expansion trailing spaces
- MUST: No dead zones on checkboxes/radios; label+control share one generous hit target
- State & navigation
- MUST: URL reflects state (deep-link filters/tabs/pagination/expanded panels) Prefer libs like nuqs
- MUST: Back/Forward restores scroll
- MUST: Links are links—use
<a>/<Link>for navigation (support Cmd/Ctrl/middle-click)
- Feedback
- SHOULD: Optimistic UI; reconcile on response; on failure show error and rollback or offer Undo
- MUST: Confirm destructive actions or provide Undo window
- MUST: Use polite
aria-livefor toasts/inline validation - SHOULD: Ellipsis (
…) for options that open follow-ups (eg, “Rename…”)
- Touch/drag/scroll
- MUST: Design forgiving interactions (generous targets, clear affordances; avoid finickiness)
- MUST: Delay first tooltip in a group; subsequent peers no delay
- MUST: Intentional
overscroll-behavior: containin modals/drawers - MUST: During drag, disable text selection and set
inerton dragged element/containers - MUST: No “dead-looking” interactive zones—if it looks clickable, it is
- Autofocus
- SHOULD: Autofocus on desktop when there’s a single primary input; rarely on mobile (to avoid layout shift)
- MUST: Honor
prefers-reduced-motion(provide reduced variant) - SHOULD: Prefer CSS > Web Animations API > JS libraries
- MUST: Animate compositor-friendly props (
transform,opacity); avoid layout/repaint props (top/left/width/height) - SHOULD: Animate only to clarify cause/effect or add deliberate delight
- SHOULD: Choose easing to match the change (size/distance/trigger)
- MUST: Animations are interruptible and input-driven (avoid autoplay)
- MUST: Correct
transform-origin(motion starts where it “physically” should)
- SHOULD: Optical alignment; adjust by ±1px when perception beats geometry
- MUST: Deliberate alignment to grid/baseline/edges/optical centers—no accidental placement
- SHOULD: Balance icon/text lockups (stroke/weight/size/spacing/color)
- MUST: Verify mobile, laptop, ultra-wide (simulate ultra-wide at 50% zoom)
- MUST: Respect safe areas (use env(safe-area-inset-*))
- MUST: Avoid unwanted scrollbars; fix overflows
- SHOULD: Inline help first; tooltips last resort
- MUST: Skeletons mirror final content to avoid layout shift
- MUST:
<title>matches current context - MUST: No dead ends; always offer next step/recovery
- MUST: Design empty/sparse/dense/error states
- SHOULD: Curly quotes (“ ”); avoid widows/orphans
- MUST: Tabular numbers for comparisons (
font-variant-numeric: tabular-numsor a mono like Geist Mono) - MUST: Redundant status cues (not color-only); icons have text labels
- MUST: Don’t ship the schema—visuals may omit labels but accessible names still exist
- MUST: Use the ellipsis character
…(not ``) - MUST:
scroll-margin-topon headings for anchored links; include a “Skip to content” link; hierarchical<h1–h6> - MUST: Resilient to user-generated content (short/avg/very long)
- MUST: Locale-aware dates/times/numbers/currency
- MUST: Accurate names (
aria-label), decorative elementsaria-hidden, verify in the Accessibility Tree - MUST: Icon-only buttons have descriptive
aria-label - MUST: Prefer native semantics (
button,a,label,table) before ARIA - SHOULD: Right-clicking the nav logo surfaces brand assets
- MUST: Use non-breaking spaces to glue terms:
10 MB,⌘ + K,Vercel SDK
- SHOULD: Test iOS Low Power Mode and macOS Safari
- MUST: Measure reliably (disable extensions that skew runtime)
- MUST: Track and minimize re-renders (React DevTools/React Scan)
- MUST: Profile with CPU/network throttling
- MUST: Batch layout reads/writes; avoid unnecessary reflows/repaints
- MUST: Mutations (
POST/PATCH/DELETE) target <500 ms - SHOULD: Prefer uncontrolled inputs; make controlled loops cheap (keystroke cost)
- MUST: Virtualize large lists (eg,
virtua) - MUST: Preload only above-the-fold images; lazy-load the rest
- MUST: Prevent CLS from images (explicit dimensions or reserved space)
- SHOULD: Layered shadows (ambient + direct)
- SHOULD: Crisp edges via semi-transparent borders + shadows
- SHOULD: Nested radii: child ≤ parent; concentric
- SHOULD: Hue consistency: tint borders/shadows/text toward bg hue
- MUST: Accessible charts (color-blind-friendly palettes)
- MUST: Meet contrast—prefer APCA over WCAG 2
- MUST: Increase contrast on
:hover/:active/:focus - SHOULD: Match browser UI to bg
- SHOULD: Avoid gradient banding (use masks when needed)
- Write concise, technical TypeScript code with accurate examples
- Use functional and declarative programming patterns; avoid classes
- Prefer iteration and modularization over code duplication
- Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError)
- Structure files: exported component, subcomponents, helpers, static content, types
- Use lowercase with dashes for directories (e.g., components/auth-wizard)
- Favor named exports for components
- Use TypeScript for all code; prefer types over interfaces UNLESS specificially for intersections (Typescript Performance)
- Avoid enums; use maps instead
- Use functional components with types
- Use the "function" keyword for pure functions
- Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements
- Use declarative JSX
- Prioritize error handling: handle errors and edge cases early
- Use early returns and guard clauses
- Implement proper error logging and user-friendly messages
- Use Zod for form validation
- Model expected errors as return values in Server Actions
- Use error boundaries for unexpected errors
- Minimize 'use client', 'useEffect', and 'setState'; favor React Server Components (RSC)
- Wrap client components in Suspense with fallback
- Use dynamic loading for non-critical components
- Optimize images: use WebP format, include size data, implement lazy loading
- Use 'nuqs' for URL search parameter state management
- Optimize Web Vitals (LCP, CLS, FID)
- Limit 'use client':
- Favor server components and Next.js SSR
- Use only for Web API access in small components
- Avoid for data fetching or state management
Ultracite enforces strict type safety, accessibility standards, and consistent code quality for JavaScript/TypeScript projects using Biome's lightning-fast formatter and linter.
- Zero configuration required
- Subsecond performance
- Maximum type safety
- AI-friendly code generation
- Analyze existing patterns in the codebase
- Consider edge cases and error scenarios
- Follow the rules below strictly
- Validate accessibility requirements
- Don't use
accessKeyattribute on any HTML element. - Don't set
aria-hidden="true"on focusable elements. - Don't add ARIA roles, states, and properties to elements that don't support them.
- Don't use distracting elements like
<marquee>or<blink>. - Only use the
scopeprop on<th>elements. - Don't assign non-interactive ARIA roles to interactive HTML elements.
- Make sure label elements have text content and are associated with an input.
- Don't assign interactive ARIA roles to non-interactive HTML elements.
- Don't assign
tabIndexto non-interactive HTML elements. - Don't use positive integers for
tabIndexproperty. - Don't include "image", "picture", or "photo" in img alt prop.
- Don't use explicit role property that's the same as the implicit/default role.
- Make static elements with click handlers use a valid role attribute.
- Always include a
titleelement for SVG elements. - Give all elements requiring alt text meaningful information for screen readers.
- Make sure anchors have content that's accessible to screen readers.
- Assign
tabIndexto non-interactive HTML elements witharia-activedescendant. - Include all required ARIA attributes for elements with ARIA roles.
- Make sure ARIA properties are valid for the element's supported roles.
- Always include a
typeattribute for button elements. - Make elements with interactive roles and handlers focusable.
- Give heading elements content that's accessible to screen readers (not hidden with
aria-hidden). - Always include a
langattribute on the html element. - Always include a
titleattribute for iframe elements. - Accompany
onClickwith at least one of:onKeyUp,onKeyDown, oronKeyPress. - Accompany
onMouseOver/onMouseOutwithonFocus/onBlur. - Include caption tracks for audio and video elements.
- Use semantic elements instead of role attributes in JSX.
- Make sure all anchors are valid and navigable.
- Ensure all ARIA properties (
aria-*) are valid. - Use valid, non-abstract ARIA roles for elements with ARIA roles.
- Use valid ARIA state and property values.
- Use valid values for the
autocompleteattribute on input elements. - Use correct ISO language/country codes for the
langattribute.
- Don't use consecutive spaces in regular expression literals.
- Don't use the
argumentsobject. - Don't use primitive type aliases or misleading types.
- Don't use the comma operator.
- Don't use empty type parameters in type aliases and interfaces.
- Don't write functions that exceed a given Cognitive Complexity score.
- Don't nest describe() blocks too deeply in test files.
- Don't use unnecessary boolean casts.
- Don't use unnecessary callbacks with flatMap.
- Use for...of statements instead of Array.forEach.
- Don't create classes that only have static members (like a static namespace).
- Don't use this and super in static contexts.
- Don't use unnecessary catch clauses.
- Don't use unnecessary constructors.
- Don't use unnecessary continue statements.
- Don't export empty modules that don't change anything.
- Don't use unnecessary escape sequences in regular expression literals.
- Don't use unnecessary fragments.
- Don't use unnecessary labels.
- Don't use unnecessary nested block statements.
- Don't rename imports, exports, and destructured assignments to the same name.
- Don't use unnecessary string or template literal concatenation.
- Don't use String.raw in template literals when there are no escape sequences.
- Don't use useless case statements in switch statements.
- Don't use ternary operators when simpler alternatives exist.
- Don't use useless
thisaliasing. - Don't use any or unknown as type constraints.
- Don't initialize variables to undefined.
- Don't use the void operators (they're not familiar).
- Use arrow functions instead of function expressions.
- Use Date.now() to get milliseconds since the Unix Epoch.
- Use .flatMap() instead of map().flat() when possible.
- Use literal property access instead of computed property access.
- Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
- Use concise optional chaining instead of chained logical expressions.
- Use regular expression literals instead of the RegExp constructor when possible.
- Don't use number literal object member names that aren't base 10 or use underscore separators.
- Remove redundant terms from logical expressions.
- Use while loops instead of for loops when you don't need initializer and update expressions.
- Don't pass children as props.
- Don't reassign const variables.
- Don't use constant expressions in conditions.
- Don't use
Math.minandMath.maxto clamp values when the result is constant. - Don't return a value from a constructor.
- Don't use empty character classes in regular expression literals.
- Don't use empty destructuring patterns.
- Don't call global object properties as functions.
- Don't declare functions and vars that are accessible outside their block.
- Make sure builtins are correctly instantiated.
- Don't use super() incorrectly inside classes. Also check that super() is called in classes that extend other constructors.
- Don't use variables and function parameters before they're declared.
- Don't use 8 and 9 escape sequences in string literals.
- Don't use literal numbers that lose precision.
- Don't use the return value of React.render.
- Make sure all dependencies are correctly specified in React hooks.
- Make sure all React hooks are called from the top level of component functions.
- Don't forget key props in iterators and collection literals.
- Don't destructure props inside JSX components in Solid projects.
- Don't define React components inside other components.
- Don't use event handlers on non-interactive elements.
- Don't assign to React component props.
- Don't use both
childrenanddangerouslySetInnerHTMLprops on the same element. - Don't use dangerous JSX props.
- Don't use Array index in keys.
- Don't insert comments as text nodes.
- Don't assign JSX properties multiple times.
- Don't add extra closing tags for components without children.
- Use
<>...</>instead of<Fragment>...</Fragment>. - Watch out for possible "wrong" semicolons inside JSX elements.
- Don't assign a value to itself.
- Don't return a value from a setter.
- Don't compare expressions that modify string case with non-compliant values.
- Don't use lexical declarations in switch clauses.
- Don't use variables that haven't been declared in the document.
- Don't write unreachable code.
- Make sure super() is called exactly once on every code path in a class constructor before this is accessed if the class has a superclass.
- Don't use control flow statements in finally blocks.
- Don't use optional chaining where undefined values aren't allowed.
- Don't have unused function parameters.
- Don't have unused imports.
- Don't have unused labels.
- Don't have unused private class members.
- Don't have unused variables.
- Make sure void (self-closing) elements don't have children.
- Don't return a value from a function with the return type 'void'
- Use isNaN() when checking for NaN.
- Make sure "for" loop update clauses move the counter in the right direction.
- Make sure typeof expressions are compared to valid values.
- Make sure generator functions contain yield.
- Don't use await inside loops.
- Don't use bitwise operators.
- Don't use expressions where the operation doesn't change the value.
- Make sure Promise-like statements are handled appropriately.
- Don't use **dirname and **filename in the global scope.
- Prevent import cycles.
- Don't use configured elements.
- Don't hardcode sensitive data like API keys and tokens.
- Don't let variable declarations shadow variables from outer scopes.
- Don't use the TypeScript directive @ts-ignore.
- Prevent duplicate polyfills from Polyfill.io.
- Don't use useless backreferences in regular expressions that always match empty strings.
- Don't use unnecessary escapes in string literals.
- Don't use useless undefined.
- Make sure getters and setters for the same property are next to each other in class and object definitions.
- Make sure object literals are declared consistently (defaults to explicit definitions).
- Use static Response methods instead of new Response() constructor when possible.
- Make sure switch-case statements are exhaustive.
- Make sure the
preconnectattribute is used when using Google Fonts. - Use
Array#{indexOf,lastIndexOf}()instead ofArray#{findIndex,findLastIndex}()when looking for the index of an item. - Make sure iterable callbacks return consistent values.
- Use
with { type: "json" }for JSON module imports. - Use numeric separators in numeric literals.
- Use object spread instead of
Object.assign()when constructing new objects. - Always use the radix argument when using
parseInt(). - Make sure JSDoc comment lines start with a single asterisk, except for the first one.
- Include a description parameter for
Symbol(). - Don't use spread (
...) syntax on accumulators. - Don't use the
deleteoperator. - Don't access namespace imports dynamically.
- Don't use namespace imports.
- Declare regex literals at the top level.
- Don't use
target="_blank"withoutrel="noopener".
- Don't use TypeScript enums.
- Don't export imported variables.
- Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
- Don't use TypeScript namespaces.
- Don't use non-null assertions with the
!postfix operator. - Don't use parameter properties in class constructors.
- Don't use user-defined types.
- Use
as constinstead of literal types and type annotations. - Use either
T[]orArray<T>consistently. - Initialize each enum member value explicitly.
- Use
export typefor types. - Use
import typefor types. - Make sure all enum members are literal values.
- Don't use TypeScript const enum.
- Don't declare empty interfaces.
- Don't let variables evolve into any type through reassignments.
- Don't use the any type.
- Don't misuse the non-null assertion operator (!) in TypeScript files.
- Don't use implicit any type on variable declarations.
- Don't merge interfaces and classes unsafely.
- Don't use overload signatures that aren't next to each other.
- Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
- Don't use global
eval(). - Don't use callbacks in asynchronous tests and hooks.
- Don't use negation in
ifstatements that haveelseclauses. - Don't use nested ternary expressions.
- Don't reassign function parameters.
- This rule lets you specify global variable names you don't want to use in your application.
- Don't use specified modules when loaded by import or require.
- Don't use constants whose value is the upper-case version of their name.
- Use
String.slice()instead ofString.substr()andString.substring(). - Don't use template literals if you don't need interpolation or special-character handling.
- Don't use
elseblocks when theifblock breaks early. - Don't use yoda expressions.
- Don't use Array constructors.
- Use
at()instead of integer index access. - Follow curly brace conventions.
- Use
else ifinstead of nestedifstatements inelseclauses. - Use single
ifstatements instead of nestedifclauses. - Use
newfor all builtins exceptString,Number, andBoolean. - Use consistent accessibility modifiers on class properties and methods.
- Use
constdeclarations for variables that are only assigned once. - Put default function parameters and optional function parameters last.
- Include a
defaultclause in switch statements. - Use the
**operator instead ofMath.pow. - Use
for-ofloops when you need the index to extract an item from the iterated array. - Use
node:assert/strictovernode:assert. - Use the
node:protocol for Node.js builtin modules. - Use Number properties instead of global ones.
- Use assignment operator shorthand where possible.
- Use function types instead of object types with call signatures.
- Use template literals over string concatenation.
- Use
newwhen throwing an error. - Don't throw non-Error values.
- Use
String.trimStart()andString.trimEnd()overString.trimLeft()andString.trimRight(). - Use standard constants instead of approximated literals.
- Don't assign values in expressions.
- Don't use async functions as Promise executors.
- Don't reassign exceptions in catch clauses.
- Don't reassign class members.
- Don't compare against -0.
- Don't use labeled statements that aren't loops.
- Don't use void type outside of generic or return types.
- Don't use console.
- Don't use control characters and escape sequences that match control characters in regular expression literals.
- Don't use debugger.
- Don't assign directly to document.cookie.
- Use
===and!==. - Don't use duplicate case labels.
- Don't use duplicate class members.
- Don't use duplicate conditions in if-else-if chains.
- Don't use two keys with the same name inside objects.
- Don't use duplicate function parameter names.
- Don't have duplicate hooks in describe blocks.
- Don't use empty block statements and static blocks.
- Don't let switch clauses fall through.
- Don't reassign function declarations.
- Don't allow assignments to native objects and read-only global variables.
- Use Number.isFinite instead of global isFinite.
- Use Number.isNaN instead of global isNaN.
- Don't assign to imported bindings.
- Don't use irregular whitespace characters.
- Don't use labels that share a name with a variable.
- Don't use characters made with multiple code points in character class syntax.
- Make sure to use new and constructor properly.
- Don't use shorthand assign when the variable appears on both sides.
- Don't use octal escape sequences in string literals.
- Don't use Object.prototype builtins directly.
- Don't redeclare variables, functions, classes, and types in the same scope.
- Don't have redundant "use strict".
- Don't compare things where both sides are exactly the same.
- Don't let identifiers shadow restricted names.
- Don't use sparse arrays (arrays with holes).
- Don't use template literal placeholder syntax in regular strings.
- Don't use the then property.
- Don't use unsafe negation.
- Don't use var.
- Don't use with statements in non-strict contexts.
- Make sure async functions actually use await.
- Make sure default clauses in switch statements come last.
- Make sure to pass a message value when creating a built-in error.
- Make sure get methods always return a value.
- Use a recommended display strategy with Google Fonts.
- Make sure for-in loops include an if statement.
- Use Array.isArray() instead of instanceof Array.
- Make sure to use the digits argument with Number#toFixed().
- Make sure to use the "use strict" directive in script files.
- Don't use
<img>elements in Next.js projects. - Don't use
<head>elements in Next.js projects. - Don't import next/document outside of pages/_document.jsx in Next.js projects.
- Don't use the next/head module in pages/_document.js on Next.js projects.
- Don't use export or module.exports in test files.
- Don't use focused tests.
- Make sure the assertion function, like expect, is placed inside an it() function call.
- Don't use disabled tests.
npx ultracite init- Initialize Ultracite in your projectnpx ultracite fix- Format and fix code automaticallynpx ultracite check- Check for issues without fixing
// ✅ Good: Comprehensive error handling
try {
const result = await fetchData();
return { success: true, data: result };
} catch (error) {
console.error("API call failed:", error);
return { success: false, error: error.message };
}
// ❌ Bad: Swallowing errors
try {
return await fetchData();
} catch (e) {
console.log(e);
}