Skip to content

Best Practices

New
Get the most out of Stylus.

Stylus is a design system based on Radix UI and Tailwind. Stylus components are used throughout the Scribe codebase to create new features.

This guide is written for employees at Scribe using Stylus. It can also help to point LLMs to this file (search for best-practices.md).

Using Stylus

Prefer props to classNames

Avoid writing too many Tailwind styles. This keeps the code lean and makes it easier to update styles in the future. If a designer has proposed a component with slight changes from what already exists, ask if it’s possible to use the existing styles.

<Button className="bg-red-500">
<Button variant="danger">

Prefer handling spacing via the parent element

Avoid setting margin on children where possible. Working with margins on children makes it more difficult to reason about conditional components. When laying out components in a group, prefer a flex or grid container with gap styles.

<div>
<Child />
<Child className="ml-2" />
</div>
<div className="flex gap-2">
<Child />
<Child />
</div>

Always provide accessible descriptions

To ensure that screen reader users can access Scribe, always provide text descriptions to icon-only buttons and non-decorative images.

<Button icon={faUserPlus} />
<Button icon={faUserPlus} screenReaderLabel="Add user" />
<Tooltip>
<TooltipTrigger asChild>
<Button icon={faUserPlus} />
</TooltipTrigger>
<TooltipContent>Add user</TooltipContent>
</Tooltip>

Avoid an excessive DOM size

Attempt to layout most things without adding additional div wrappers. Per Google:

A large DOM tree can slow down page performance in multiple ways. […] In general, look for ways to create DOM nodes only when needed, and destroy nodes when they’re no longer needed.

Components without any content should generally return null rather than an empty DOM node.

Adding To Stylus

Only add components or props with sufficient need

In general, new props or components should not be added until there are three or more places they are in use. A strong contender for addition to the design system will be able to work well in multiple locations and across multiple features. One-off growth experiments or feature-specific UI additions are unlikely to be added to Stylus.

Favor composition over configuration

When building complex components, prefer composing smaller components rather than adding many configuration props.

<ComplexCard
hasHeader
headerTitle="Title"
headerAction={<Button>Edit</Button>}
footer={<Button>Save</Button>} />
<Card>
<CardHeader>
<CardTitle>Title</CardTitle>
<Button>Edit</Button>
</CardHeader>
<CardContent>...</CardContent>
<CardFooter>
<Button>Save</Button>
</CardFooter>
</Card>

Follow consistent naming conventions

  • Use PascalCase for component names: UserProfile, NavigationBar
  • Use camelCase for props: isLoading, onValueChange
  • Prefix boolean props with is, has, or should: isDisabled, hasError, shouldAutoFocus
  • Use Tailwind for CSS classes, and use class-variance-authority for organizing and defining variant styles

Maintain consistent prop patterns

  • Use onValueChange instead of onChange for controlled components
  • Use defaultValue for uncontrolled components
  • Use asChild pattern for polymorphic components (following Radix patterns)

Test accessibility

Stylus components should be accessible by default.

  • Text descriptions and appropriate aria roles should be supplied where necessary.
  • Prefer semantic HTML elements like section, aside, and header to using div.
  • Always ensure that interactive elements have keyboard focus styles using focus-visible.
  • With very few exceptions, do not put onClick handlers on div—use an HTML button.

Write tests that focus on behavior

Test what users do, not implementation details.

// Good: Tests user behavior
test('submits form when user clicks submit button', async () => {
render(<ContactForm onSubmit={mockSubmit} />);
await user.type(screen.getByLabelText(/email/i), '[email protected]');
await user.click(screen.getByRole('button', { name: /submit/i }));
expect(mockSubmit).toHaveBeenCalledWith({ email: '[email protected]' });
});

Use the scaffolding script for new components

You can scaffold new components from stylus-ui using yarn scaffold ComponentName ex. yarn scaffold SearchHeader. This command will scaffold a new directory containing the component, barrel file, test file and Storybook file, along with documentation in /packages/stylus-docs.

Document and test all components

  • Write basic unit tests to validate functionality.
  • Always document components inside the stylus-docs package. If props or styles change, update the docs to reflect the changes.
  • Write basic Storybook stories for each component.