Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 24 additions & 9 deletions .claude/skills/gamut-writing/references/docs-in-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,30 @@ Commented-out code — delete it. Git tracks history, and a commented block leav

## Naming

Clear names remove the need for most comments, so naming is the first documentation decision in a file.
Clear names remove the need for most comments, so naming is the first documentation decision in a file. These conventions won't cover every case, but three principles handle most of them:

- **Be readable** — the name should make sense to the next reader, not just the person who wrote it. Names are self-documentation, and abbreviations trip up agents as well as people.
- **Be consistent** — a name or convention probably already exists; find it and reuse it rather than inventing a new one.
- **Be specific** — a name should point to exactly one thing. Avoid catch-alls like `data`, `value`, or `handler`.

**Components**

- `PascalCase`: `Button`, `UserProfile`, `NavigationMenu`
- The folder matches the component name, and the file inside matches it too: `Button/Button.tsx`, `UserProfile/UserProfile.tsx`
- Names that indicate purpose: `SkipToContent`, `RadialProgress`, `Toggle`
- Avoid `Component`, `Container`, or `Wrapper` without further context — they describe the shape of the code rather than what it does

**Component props**

- The native HTML attribute name, when one exists: `disabled`, `checked`, `readOnly`, `required`, `hidden`, `value`, `placeholder`, `href`
- Exception: when the native attribute name is ambiguous about type — `open` reads as a verb, with no clue it is a boolean rather than a function — prefer a readable prefixed name, e.g. `is`, instead: `isOpen`.
- A boolean prop without a native attribute takes an `is`, `has`, or `can` prefix: `isVisible`, `hasWatermark`, `canBeProtected`
- Plurals for array props: `books`, `items`, `activeLocations`
- An enum over a cluster of exclusive booleans: `variant="primary" | "secondary"`, not separate `isPrimary` and `isSecondary` props
- An `on` prefix followed by the event or callback in present tense, whether the event is native or invented: `onChange`, `onClick`, `onClose`, `onSelect`
- The state the component is usually in, and default to that state: `visible` (defaulting to `true`) beats `hidden` (defaulting to `false`) when a component is visible most of the time
- What the prop controls, not how it's built: `size="sm"`, not `smallVariant` or `useSmallStyles`
- Logical property names over physical or visual ones, so the name holds up under RTL: `leading`/`trailing` over `left`/`right`, `start`/`end` over `top`/`bottom`

**Variables and constants**

Expand All @@ -120,18 +143,10 @@ Clear names remove the need for most comments, so naming is the first documentat
- Booleans take an `is`, `has`, `should`, or `can` prefix: `isVisible`, `hasError`, `shouldRender`
- `SCREAMING_SNAKE_CASE` for true constants: `MAX_RETRY_COUNT`, `DEFAULT_TIMEOUT`
- Plurals for arrays and collections: `users`, `menuItems`
- Single letters only in short loops or mathematical operations

**Functions and methods**

- `camelCase`, starting with a verb that names the action: `get`, `set`, `fetch`, `handle`, `render`, `calculate`
- Event handlers take a `handle` prefix: `handleSubmit`, `handleClickOutside`
- Functions returning a boolean read as a question: `isValidEmail`, `canAccessResource`, `hasPermission`
- Concise but descriptive: `fetchUserProfile`, not `getUserProfileDataFromAPI`

**Components**

- `PascalCase`: `Button`, `UserProfile`, `NavigationMenu`
- The folder matches the component name, and the file inside matches it too: `Button/Button.tsx`, `UserProfile/UserProfile.tsx`
- Names that indicate purpose: `SkipToContent`, `RadialProgress`, `Toggle`
- Avoid `Component`, `Container`, or `Wrapper` without further context — they describe the shape of the code rather than what it does
1 change: 1 addition & 0 deletions packages/gamut-docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ export default defineConfig({
{ slug: 'guides/migrating-to-logical-properties' },
{ slug: 'guides/supporting-dark-mode' },
{ slug: 'guides/theming-your-app' },
{ slug: 'guides/writing-guidance' },
{
// `autogenerate` labels nested groups from the raw directory
// name, so hyphenated directories (kept short for tooling
Expand Down
1 change: 1 addition & 0 deletions packages/gamut-docs/src/content/docs/concepts/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ Explanation: how and why the system is built the way it is, for when you want to
- [Color modes](/concepts/color-modes/)
- [Brand](/concepts/brand/)
- [Best practices](/concepts/best-practices/)
- [Naming conventions rationale](/concepts/naming-conventions-rationale/)
- [Voice & tone](/concepts/voice-and-tone/)
- [FAQs](/concepts/faqs/)
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
title: Naming conventions rationale
description: Why Gamut's naming conventions land where they do, not just what they are.
---

The [Writing guidance](/guides/writing-guidance/) guide has the naming rules themselves. This page provides our reasoning behind the ones that aren't self-evident from the rule alone.

## Using native HTML attributes

This practice is using vocabulary readers already know from having worked with HTML. Let's use `disabled` as an example, it makes it so that an interactive element isn't interactive anymore. However, there are other aspects that we don't want like the removal of an element from the tab order entirely. What we usually want is the disabled styling, plus `aria-disabled`, plus an `aria-describedby` tooltip explaining why the control is unavailable. Thefore, naming the prop `disabled` anyway means a developer who already knows HTML can quickly implement these features with a prop they're already familiar with.

However, some HTML native prop name are ambiguous — it's hard to know from the name alone what the job of the prop is. For instance, `open` reads as a verb, with nothing in the word itself signaling that it's a boolean rather than a function to call. In this case, we'd opt for readability, using `isOpen` instead of `open` to ensure developers aren't confused by what the prop does.

## Using enums for props that can take on multiple different values

Using enums to for props where it can take on different values makes it easier to ensure that the set styling is correct.

Let's explain using an example, if we use props such like `isPrimary` and `isSecondary` then we could end up with a situation that both can be `true` at the same time — which is very likely not the intended effect. Using a ``variant: 'primary' | 'secondary'` prevents this combination and gives an extra assurance from TypeScript this prop will only allow for the correct enums.

## Using the prodominent state

If a prop is used to determine the state of the component, consider what the default state of that component is supposed to be, and name the prop (and its default) around that state rather than its opposite.

Let's explore using an example: `hideLabel` on `ConnectedFormGroup` has no default, so it's falsy until a caller sets it — the label shows by default, the predominant state here. Hiding it is the deliberate exception, and even then nothing is torn down: a hidden label still renders for screen readers instead of disappearing outright. Naming the prop after the exception, not the common case, means `false` doesn't need a comment to explain what it does.

## Naming the interface, not the implementation

`size="sm"` describes what the prop controls, not how it's built, so it keeps meaning the same thing even if the implementation behind "small" changes later. `smallVariant` or `useSmallStyles` name how "small" happens to be built today instead — the moment that changes, a name like `useSmallStyles` becomes actively wrong.

## Staying correct in both reading directions

A property named `left` means something different once a page mirrors for a right-to-left locale — the padding or margin a developer set for one side silently lands on the other. `leading`/`trailing` and `start`/`end` name a position relative to the reading direction instead of the screen, so the same prop value is correct in both directions with no conditional logic to flip it.

## Names you can act on

`Container` and `Wrapper` describe the shape of the code, not what it's for. Both just mean something wraps something else. Six months later, nobody can tell from the name alone whether it's safe to delete, safe to reuse, or load-bearing for three other components. `SkipToContent` or `RadialProgress` answers that question on sight. Matching the folder, file, and export to the same name keeps that answer one lookup away, whether someone arrives at the file through an import statement, a fuzzy search, or jump-to-definition.
Loading
Loading