Best Practices
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.
// Don't override styles which already exist<Button className="bg-red-500">// Do use existing variants and props<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.
// Don't use margins on most child elements<div> <Child /> <Child className="ml-2" />-{" "}</div>// Do add gap styles on the parent<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.
// Don't use icon buttons without descriptive text<Button icon={faUserPlus} />// Do add labels for screen readers<Button icon={faUserPlus} aria-label="Add user" />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.
// Don't create overly complex prop APIs<ComplexCard hasHeader headerTitle="Title" headerAction={<Button>Edit</Button>} footer={<Button>Save</Button>}/>// Do use composition patterns<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, orshould:isDisabled,hasError,shouldAutoFocus - Use Tailwind for CSS classes, and use class-variance-authority for organizing and defining variant styles
Maintain consistent prop patterns
- Use
onValueChangeinstead ofonChangefor controlled components - Use
defaultValuefor uncontrolled components - Use
asChildpattern for polymorphic components (following Radix patterns)
Test accessibility
Stylus components should be accessible by default.
- Text descriptions and appropriate
ariaroles should be supplied where necessary. - Prefer semantic HTML elements like
section,aside, andheaderto usingdiv. - Always ensure that interactive elements have keyboard focus styles using
focus-visible. - With very few exceptions, do not put
onClickhandlers ondiv—use an HTMLbutton.
Write tests that focus on behavior
Test what users do, not implementation details.
// Good: Tests user behaviortest("submits form when user clicks submit button", async () => { render(<ContactForm onSubmit={mockSubmit} />);
await user.type(screen.getByLabelText(/email/i), "test@example.com"); await user.click(screen.getByRole("button", { name: /submit/i }));
expect(mockSubmit).toHaveBeenCalledWith({ email: "test@example.com" });});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-docspackage. If props or styles change, update the docs to reflect the changes. - Write basic Storybook stories for each component.