From 0cd16bd3868966679ddc6c39f16d81383ca771e7 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Thu, 1 Oct 2026 11:19:44 -0400 Subject: [PATCH 1/9] make updates to contribution docs --- .../docs/guides/contributing-to-gamut.md | 44 +++++++++++++------ 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/packages/gamut-docs/src/content/docs/guides/contributing-to-gamut.md b/packages/gamut-docs/src/content/docs/guides/contributing-to-gamut.md index cd4831badf..cac1f94e2a 100644 --- a/packages/gamut-docs/src/content/docs/guides/contributing-to-gamut.md +++ b/packages/gamut-docs/src/content/docs/guides/contributing-to-gamut.md @@ -36,7 +36,37 @@ export const MyComponent: React.FC = ( ### Naming conventions -Clear, descriptive names reduce the need for comments and make code self-documenting. +We’ve established these conventions to help provide guidance on one of the most difficult exercises in programming — naming. These guidelines are not hard and fast rules, they won’t cover every single case, but generally, they will provide a frame of mind to write helpful names for anyone reading this code, including agents. + +#### Principles + +1. Be consistent - there’s a good chance that a name or convention already exists, check for it, use it and continue to use it. + +2. Be readable - ensure that the name can be understood by other people, not just your current self. + +- Names should serve as self-documentation +- Abbreviations can trip up agents, and even people. + +3. Be specific - a name should point to exactly one thing; avoid catch-alls like data, value, or handler. + +**Components** + +- Use `PascalCase`: `Button`, `UserProfile`, `NavigationMenu`. +- Name the folder to match the component, and the file inside it to match the folder: `Button/Button.tsx`. +- Use names that indicate purpose — `SkipToContent`, `RadialProgress`, `Toggle` — and avoid generic ones like `Component`, `Container`, or `Wrapper` without further context. + +**Component Props** + +- Use the native HTML attribute name when one exists. + - e.g. `disabled`, `checked`, `readOnly`, `required`, `hidden`, `open`, `value`, `placeholder`, `href` +- Boolean props that don’t have a native attribute should include a prefix: `is`, `has`, `can`, etc... e.g. `isVisible`, `hasWatermark`, `canBeProtected`. +- Array props should be the plural noun. e.g. `books`, `items`, `activeLocations` +- Use an enum over a cluster of exclusive booleans. + - `variant="primary" | "secondary"` is better than two separate props `isPrimary` + `isSecondary`. +- Event and callback props have an on prefix followed by the event/callback in present tense, e.g. `onChange`, `onClick`, `onClose`, `onSelect`. Same pattern whether the event is native or invented. +- Name the state, not its negation. + - e.g. `visible`, not `hidden={false}`. +- Name what the prop controls, not how it's built. `size="sm"`, not `smallVariant` or `useSmallStyles`. **Variables and constants** @@ -44,7 +74,6 @@ Clear, descriptive names reduce the need for comments and make code self-documen - Use names that reveal purpose: `filteredResults`, not `arr`. - Prefix booleans with `is`, `has`, `should`, or `can`: `isVisible`, `hasError`, `shouldRender`. - Use `SCREAMING_SNAKE_CASE` for true constants: `MAX_RETRY_COUNT`, `DEFAULT_TIMEOUT`. -- Avoid single-letter names, except in short loops or math. - Use plural names for arrays and collections: `users`, `menuItems`. **Functions and methods** @@ -54,17 +83,6 @@ Clear, descriptive names reduce the need for comments and make code self-documen - Phrase a boolean-returning function as a question: `isValidEmail`, `canAccessResource`, `hasPermission`. - Keep names concise but descriptive: `fetchUserProfile`, not `getUserProfileDataFromAPI`. -**Components** - -- Use `PascalCase`: `Button`, `UserProfile`, `NavigationMenu`. -- Name the folder to match the component, and the file inside it to match the folder: `Button/Button.tsx`. -- Use names that indicate purpose — `SkipToContent`, `RadialProgress`, `Toggle` — and avoid generic ones like `Component`, `Container`, or `Wrapper` without further context. - -### Consistency - -- Use a single term for the same concept, in the heading, body copy, and code examples alike — and don't reuse a term for two different concepts. -- Keep component naming consistent across packages, following the patterns established by existing components. - ### Code comments Comments should explain _why_ code exists, not _what_ it does — well-named variables and functions already handle the "what." Reserve comments for non-obvious decisions, complex logic, and important context: From e825eddf33191e727328c39b9719a8907f07406f Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Thu, 1 Oct 2026 11:43:27 -0400 Subject: [PATCH 2/9] updated gamut-writing skill --- .../gamut-writing/references/docs-in-code.md | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/.claude/skills/gamut-writing/references/docs-in-code.md b/.claude/skills/gamut-writing/references/docs-in-code.md index ddf993a8f0..77ba5af085 100644 --- a/.claude/skills/gamut-writing/references/docs-in-code.md +++ b/.claude/skills/gamut-writing/references/docs-in-code.md @@ -111,7 +111,28 @@ 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 consistent** — a name or convention probably already exists; find it and reuse it rather than inventing a new one. +- **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 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`, `open`, `value`, `placeholder`, `href` +- 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, not its negation: `visible`, not `hidden={false}` +- What the prop controls, not how it's built: `size="sm"`, not `smallVariant` or `useSmallStyles` **Variables and constants** @@ -120,7 +141,6 @@ 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** @@ -128,10 +148,3 @@ Clear names remove the need for most comments, so naming is the first documentat - 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 From 9718bd00db468cb395b629d423f5bbd6dec36a40 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Fri, 2 Oct 2026 11:03:52 -0400 Subject: [PATCH 3/9] address part of workshop feedback --- .../src/content/docs/guides/contributing-to-gamut.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/gamut-docs/src/content/docs/guides/contributing-to-gamut.md b/packages/gamut-docs/src/content/docs/guides/contributing-to-gamut.md index cac1f94e2a..271f9dc510 100644 --- a/packages/gamut-docs/src/content/docs/guides/contributing-to-gamut.md +++ b/packages/gamut-docs/src/content/docs/guides/contributing-to-gamut.md @@ -40,13 +40,13 @@ We’ve established these conventions to help provide guidance on one of the mos #### Principles -1. Be consistent - there’s a good chance that a name or convention already exists, check for it, use it and continue to use it. - -2. Be readable - ensure that the name can be understood by other people, not just your current self. +1. Be readable - ensure that the name can be understood by other people, not just your current self. - Names should serve as self-documentation - Abbreviations can trip up agents, and even people. +2. Be consistent - there’s a good chance that a name or convention already exists, check for it, use it and continue to use it. + 3. Be specific - a name should point to exactly one thing; avoid catch-alls like data, value, or handler. **Components** @@ -58,7 +58,8 @@ We’ve established these conventions to help provide guidance on one of the mos **Component Props** - Use the native HTML attribute name when one exists. - - e.g. `disabled`, `checked`, `readOnly`, `required`, `hidden`, `open`, `value`, `placeholder`, `href` + - e.g. `disabled`, `checked`, `readOnly`, `required`, `hidden`, `value`, `placeholder`, `href` + - caveat: if an attribute is ambigious, e.g. `open` where it is verb but unclear if it is supposed to be a boolean or function then opt for readability, use `isOpen` since it is supposed to be a boolean value - Boolean props that don’t have a native attribute should include a prefix: `is`, `has`, `can`, etc... e.g. `isVisible`, `hasWatermark`, `canBeProtected`. - Array props should be the plural noun. e.g. `books`, `items`, `activeLocations` - Use an enum over a cluster of exclusive booleans. @@ -66,6 +67,8 @@ We’ve established these conventions to help provide guidance on one of the mos - Event and callback props have an on prefix followed by the event/callback in present tense, e.g. `onChange`, `onClick`, `onClose`, `onSelect`. Same pattern whether the event is native or invented. - Name the state, not its negation. - e.g. `visible`, not `hidden={false}`. +- Name the state for which 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. - Name what the prop controls, not how it's built. `size="sm"`, not `smallVariant` or `useSmallStyles`. **Variables and constants** From 71cdb09910b870b4062628fa1f5e442309241795 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Fri, 2 Oct 2026 11:16:11 -0400 Subject: [PATCH 4/9] revise agent skill --- .claude/skills/gamut-writing/references/docs-in-code.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.claude/skills/gamut-writing/references/docs-in-code.md b/.claude/skills/gamut-writing/references/docs-in-code.md index 77ba5af085..e24347c7be 100644 --- a/.claude/skills/gamut-writing/references/docs-in-code.md +++ b/.claude/skills/gamut-writing/references/docs-in-code.md @@ -113,8 +113,8 @@ Commented-out code — delete it. Git tracks history, and a commented block leav 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 consistent** — a name or convention probably already exists; find it and reuse it rather than inventing a new one. - **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** @@ -126,12 +126,13 @@ Clear names remove the need for most comments, so naming is the first documentat **Component props** -- The native HTML attribute name, when one exists: `disabled`, `checked`, `readOnly`, `required`, `hidden`, `open`, `value`, `placeholder`, `href` +- 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, not its negation: `visible`, not `hidden={false}` +- 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` **Variables and constants** From 8bdec54206015c23b619fc8d6b31042ae2699583 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Fri, 2 Oct 2026 14:02:04 -0400 Subject: [PATCH 5/9] added RTL clause and moved writing guides to different file --- .../gamut-writing/references/docs-in-code.md | 1 + .../docs/guides/contributing-to-gamut.md | 192 +---------------- .../src/content/docs/guides/index.md | 1 + .../content/docs/guides/writing-guidance.mdx | 196 ++++++++++++++++++ 4 files changed, 200 insertions(+), 190 deletions(-) create mode 100644 packages/gamut-docs/src/content/docs/guides/writing-guidance.mdx diff --git a/.claude/skills/gamut-writing/references/docs-in-code.md b/.claude/skills/gamut-writing/references/docs-in-code.md index e24347c7be..5145cabec7 100644 --- a/.claude/skills/gamut-writing/references/docs-in-code.md +++ b/.claude/skills/gamut-writing/references/docs-in-code.md @@ -134,6 +134,7 @@ Clear names remove the need for most comments, so naming is the first documentat - 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** diff --git a/packages/gamut-docs/src/content/docs/guides/contributing-to-gamut.md b/packages/gamut-docs/src/content/docs/guides/contributing-to-gamut.md index 271f9dc510..e80d9ef88c 100644 --- a/packages/gamut-docs/src/content/docs/guides/contributing-to-gamut.md +++ b/packages/gamut-docs/src/content/docs/guides/contributing-to-gamut.md @@ -34,90 +34,7 @@ export const MyComponent: React.FC = ( }; ``` -### Naming conventions - -We’ve established these conventions to help provide guidance on one of the most difficult exercises in programming — naming. These guidelines are not hard and fast rules, they won’t cover every single case, but generally, they will provide a frame of mind to write helpful names for anyone reading this code, including agents. - -#### Principles - -1. Be readable - ensure that the name can be understood by other people, not just your current self. - -- Names should serve as self-documentation -- Abbreviations can trip up agents, and even people. - -2. Be consistent - there’s a good chance that a name or convention already exists, check for it, use it and continue to use it. - -3. Be specific - a name should point to exactly one thing; avoid catch-alls like data, value, or handler. - -**Components** - -- Use `PascalCase`: `Button`, `UserProfile`, `NavigationMenu`. -- Name the folder to match the component, and the file inside it to match the folder: `Button/Button.tsx`. -- Use names that indicate purpose — `SkipToContent`, `RadialProgress`, `Toggle` — and avoid generic ones like `Component`, `Container`, or `Wrapper` without further context. - -**Component Props** - -- Use the native HTML attribute name when one exists. - - e.g. `disabled`, `checked`, `readOnly`, `required`, `hidden`, `value`, `placeholder`, `href` - - caveat: if an attribute is ambigious, e.g. `open` where it is verb but unclear if it is supposed to be a boolean or function then opt for readability, use `isOpen` since it is supposed to be a boolean value -- Boolean props that don’t have a native attribute should include a prefix: `is`, `has`, `can`, etc... e.g. `isVisible`, `hasWatermark`, `canBeProtected`. -- Array props should be the plural noun. e.g. `books`, `items`, `activeLocations` -- Use an enum over a cluster of exclusive booleans. - - `variant="primary" | "secondary"` is better than two separate props `isPrimary` + `isSecondary`. -- Event and callback props have an on prefix followed by the event/callback in present tense, e.g. `onChange`, `onClick`, `onClose`, `onSelect`. Same pattern whether the event is native or invented. -- Name the state, not its negation. - - e.g. `visible`, not `hidden={false}`. -- Name the state for which 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. -- Name what the prop controls, not how it's built. `size="sm"`, not `smallVariant` or `useSmallStyles`. - -**Variables and constants** - -- Use `camelCase`: `userName`, `isLoading`, `itemCount`. -- Use names that reveal purpose: `filteredResults`, not `arr`. -- Prefix booleans with `is`, `has`, `should`, or `can`: `isVisible`, `hasError`, `shouldRender`. -- Use `SCREAMING_SNAKE_CASE` for true constants: `MAX_RETRY_COUNT`, `DEFAULT_TIMEOUT`. -- Use plural names for arrays and collections: `users`, `menuItems`. - -**Functions and methods** - -- Use `camelCase`, starting with a verb that describes the action: `get`, `set`, `fetch`, `handle`, `render`, `calculate`. -- Prefix event handlers with `handle`: `handleSubmit`, `handleClickOutside`. -- Phrase a boolean-returning function as a question: `isValidEmail`, `canAccessResource`, `hasPermission`. -- Keep names concise but descriptive: `fetchUserProfile`, not `getUserProfileDataFromAPI`. - -### Code comments - -Comments should explain _why_ code exists, not _what_ it does — well-named variables and functions already handle the "what." Reserve comments for non-obvious decisions, complex logic, and important context: - -```tsx -// Use binary search for O(log n) performance on sorted arrays -const index = binarySearch(sortedArray, target); - -// Per WCAG 2.2, focus must return to the trigger element on close -previousFocusRef.current?.focus(); - -// Safari doesn't support :focus-visible, fallback to :focus -// TODO: Remove when Safari 15+ is the minimum supported version - -// Delay state update to avoid a race condition with async validation -setTimeout(() => setIsValid(true), 0); -``` - -Skip a comment when the code is already self-explanatory: - -```tsx -// Avoid: the comment only restates the code -// Set loading to true -setIsLoading(true); - -// Prefer: the code is already self-documenting -setIsLoading(true); -``` - -Delete commented-out code instead of leaving it in place — git already tracks its history. - -**Style:** use `//` for single-line comments, with a space after the slashes; use `/** */` JSDoc comments on exports (functions, types, components); write complete sentences with proper punctuation; keep comments up to date as the code changes. +Naming, code comments, formatting, and linking conventions all live in the [Writing guidance](/guides/writing-guidance/) guide — read it before writing or revising a component, its props, or its documentation. ### Props documentation @@ -148,14 +65,7 @@ Add unit tests in a `__tests__/MyComponent-test.tsx` file within the component's ## Writing stories -Every component needs Storybook stories demonstrating its use, in a `.stories.tsx` file alongside a `.mdx` documentation file. This structure is the source every component page's `StoryEmbed`s pull from, so both files need to stay accurate. - -### File structure and naming - -The folder structure mirrors both Gamut's atomic-design tiers and the generated Storybook hierarchy. Find the right folder under `packages/styleguide/src/lib` (`Atoms`, `Molecules`, `Organisms`, and so on), then create a new folder containing `ComponentName.stories.tsx` and `ComponentName.mdx` — plus any example or utility files the stories need. - -- Non-component files with more than one word use a space and sentence case: `General principles.mdx`. -- Component-related files use the component's own `PascalCase` name: `RadialProgress.mdx`. +Every component needs Storybook stories demonstrating its use, in a `.stories.tsx` file alongside a `.mdx` documentation file. This structure is the source every component page's `StoryEmbed`s pull from, so both files need to stay accurate. File structure and naming conventions live in the [Writing guidance](/guides/writing-guidance/) guide. ### Writing the `.mdx` documentation file @@ -197,104 +107,6 @@ export const Default: Story = { When a folder holds more than one related component or story, add an `About.mdx` file as its landing page — for example, the Icons folder's `About.mdx` links out to its Mini and Regular sub-pages. Give it a clear overview of what the folder contains and how its components relate, organized by importance or usage frequency, and keep it concise — it's an entry point, not detailed documentation. -## Formatting - -**Numbers and units** - -- Use numerals for all numbers, with commas for thousands (1,000). -- Use standard units — `px`, `rem`, `em`, `%`. -- In prose, put a space between a number and its unit ("16 pixels"); in code, don't ("16px"). - -**Lists** - -- Bulleted lists are for unordered items — keep them in parallel structure, and end each item with a period only if it's a complete sentence. -- Numbered lists are for sequential steps or prioritized items — start each item with a capital letter. - -**Code blocks** - -- Use triple backticks with a language identifier (` ```tsx `, ` ```javascript `, ` ```css `). -- Include comments for complex examples, and keep examples concise and focused. - -**Headings** - -- Start at the second level (`##`) — the first level is set automatically from the page's title. -- Don't skip a heading level; it breaks the reading order. - -**Whitespace** - -- Separate sections with a blank line, and never stack multiple consecutive blank lines. -- Indent code consistently — 2 spaces for TypeScript/TSX, with tabs set to 2 spaces if you use them. - -## Linking - -**Internal links** - -In Storybook's own `.mdx` files, use the `LinkTo` component with an `id` matching the target story's id: - -```tsx -import { LinkTo } from '~styleguide/blocks'; - -Animation; -``` - -- Link text describes the destination, not the action — "See the Stories page," not "Click here." -- Make link text meaningful out of context: "the Stories page," not "click here." -- Link a component's name to its documentation. -- Verify the link actually works. -- Use at least 2–3 words, so the link is easy to click. -- Give each link unique text when more than one appears on the same page. - -**External links** - -Use a plain Markdown link for something like an external tool or reference — most renderers already open these in a new tab: - -```markdown -[GitHub Repository](https://github.com/Codecademy/gamut) -``` - -For more control over the link itself — for example, inside a component that needs an `Anchor` — pass `target="_blank"` together with `rel="noreferrer"` for security, but don't force that behavior unless it's actually needed; a reader can already choose to open a link in a new tab themselves. - -## Referencing code - -**Code in text** - -- Use backticks for inline code: props, CSS properties, component names, prop values (`onClick`, `flex-direction`, `Box`, `true`). -- Use backticks for file and package names too: `Button.tsx`, `package.json`, `@skillsoft/gamut`. -- Refer to a component as "the `Box` component" on first mention, then "the component" afterward. -- Keep a component name singular even when referring to several instances — "these `Box` components," not "these `Boxes`." - -**Code samples** - -Include the necessary imports, use realistic and working examples, add comments for complex logic, keep each example focused on one concept, and use TypeScript types: - -```tsx -import { StrokeButton } from '@skillsoft/gamut'; - -export const SimpleButtonExample: React.FC = () => ( - Click me -); -``` - -**Command-line syntax** - -Use shell (`sh`) syntax highlighting, skip the prompt symbol (`$`), and put one command per block unless several are directly related: - -```bash -yarn add @skillsoft/gamut -``` - -**File paths** - -Use backticks for file paths (`packages/gamut/src/Button/index.tsx`); use a relative path when the context already makes it clear (`./types.ts`), and a workspace-root path when it doesn't. Say "in the `ComponentName.mdx` file" for a code location, rather than a bare path. - -**UI element references** - -- Bold a UI label: **Next**, **Back**, **Close**. -- Describe where an element is: "Click the **Theme Switcher** (paintbrush icon)." -- Use sentence case: "the **Show code** button." -- Prefer device-agnostic language — "click," not a touch- or mouse-specific verb. -- Avoid directional language like "the form on the right" or "the section above" — say "the following form" or "the previous section" instead. - ## Pull requests Fill out the pull request template, including links to the corresponding design file and JIRA ticket. diff --git a/packages/gamut-docs/src/content/docs/guides/index.md b/packages/gamut-docs/src/content/docs/guides/index.md index 7811e054a5..88f28cf7e8 100644 --- a/packages/gamut-docs/src/content/docs/guides/index.md +++ b/packages/gamut-docs/src/content/docs/guides/index.md @@ -12,3 +12,4 @@ How-to guides for cross-cutting tasks that don't have a home under a single comp - [Writing UX copy](/guides/writing-ux-copy/) - [Migrating to logical properties](/guides/migrating-to-logical-properties/) - [Contributing to Gamut](/guides/contributing-to-gamut/) +- [Writing guidance](/guides/writing-guidance/) diff --git a/packages/gamut-docs/src/content/docs/guides/writing-guidance.mdx b/packages/gamut-docs/src/content/docs/guides/writing-guidance.mdx new file mode 100644 index 0000000000..8f5fbebc1f --- /dev/null +++ b/packages/gamut-docs/src/content/docs/guides/writing-guidance.mdx @@ -0,0 +1,196 @@ +--- +title: Writing guidance +description: Naming, comment, formatting, and linking conventions for Gamut code and documentation. +--- + +## Naming conventions + +We’ve established these conventions to help provide guidance on one of the most difficult exercises in programming — naming. These guidelines are not hard and fast rules, they won’t cover every single case, but generally, they will provide a frame of mind to write helpful names for anyone reading this code, including agents. + +### Principles + +1. Be readable - ensure that the name can be understood by other people, not just your current self. + +- Names should serve as self-documentation +- Abbreviations can trip up agents, and even people. + +2. Be consistent - there’s a good chance that a name or convention already exists, check for it, use it and continue to use it. + +3. Be specific - a name should point to exactly one thing; avoid catch-alls like data, value, or handler. + +**Components** + +- Use `PascalCase`: `Button`, `UserProfile`, `NavigationMenu`. +- Name the folder to match the component, and the file inside it to match the folder: `Button/Button.tsx`. +- Use names that indicate purpose — `SkipToContent`, `RadialProgress`, `Toggle` — and avoid generic ones like `Component`, `Container`, or `Wrapper` without further context. + +**Component Props** + +- Use the native HTML attribute name when one exists. + - e.g. `disabled`, `checked`, `readOnly`, `required`, `hidden`, `value`, `placeholder`, `href` + - caveat: if an attribute is ambigious, e.g. `open` where it is verb but unclear if it is supposed to be a boolean or function then opt for readability, use `isOpen` since it is supposed to be a boolean value +- Boolean props that don’t have a native attribute should include a prefix: `is`, `has`, `can`, etc... e.g. `isVisible`, `hasWatermark`, `canBeProtected`. +- Array props should be the plural noun. e.g. `books`, `items`, `activeLocations` +- Use an enum over a cluster of exclusive booleans. + - `variant="primary" | "secondary"` is better than two separate props `isPrimary` + `isSecondary`. +- Event and callback props have an on prefix followed by the event/callback in present tense, e.g. `onChange`, `onClick`, `onClose`, `onSelect`. Same pattern whether the event is native or invented. +- Name the state, not its negation. + - e.g. `visible`, not `hidden={false}`. +- Name the state for which 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. +- Name what the prop controls, not how it's built. `size="sm"`, not `smallVariant` or `useSmallStyles`. +- Opt for logical property names instead of physical or visual names to allow for RTL conversions + — `leading`/`trailing` over `left`/`right`, `start`/`end` over `top`/`bottom` + +**Variables and constants** + +- Use `camelCase`: `userName`, `isLoading`, `itemCount`. +- Use names that reveal purpose: `filteredResults`, not `arr`. +- Prefix booleans with `is`, `has`, `should`, or `can`: `isVisible`, `hasError`, `shouldRender`. +- Use `SCREAMING_SNAKE_CASE` for true constants: `MAX_RETRY_COUNT`, `DEFAULT_TIMEOUT`. +- Use plural names for arrays and collections: `users`, `menuItems`. + +**Functions and methods** + +- Use `camelCase`, starting with a verb that describes the action: `get`, `set`, `fetch`, `handle`, `render`, `calculate`. +- Prefix event handlers with `handle`: `handleSubmit`, `handleClickOutside`. +- Phrase a boolean-returning function as a question: `isValidEmail`, `canAccessResource`, `hasPermission`. +- Keep names concise but descriptive: `fetchUserProfile`, not `getUserProfileDataFromAPI`. + +## Code comments + +Comments should explain _why_ code exists, not _what_ it does — well-named variables and functions already handle the "what." Reserve comments for non-obvious decisions, complex logic, and important context: + +```tsx +// Use binary search for O(log n) performance on sorted arrays +const index = binarySearch(sortedArray, target); + +// Per WCAG 2.2, focus must return to the trigger element on close +previousFocusRef.current?.focus(); + +// Safari doesn't support :focus-visible, fallback to :focus +// TODO: Remove when Safari 15+ is the minimum supported version + +// Delay state update to avoid a race condition with async validation +setTimeout(() => setIsValid(true), 0); +``` + +Skip a comment when the code is already self-explanatory: + +```tsx +// Avoid: the comment only restates the code +// Set loading to true +setIsLoading(true); + +// Prefer: the code is already self-documenting +setIsLoading(true); +``` + +Delete commented-out code instead of leaving it in place — git already tracks its history. + +**Style:** use `//` for single-line comments, with a space after the slashes; use `/** */` JSDoc comments on exports (functions, types, components); write complete sentences with proper punctuation; keep comments up to date as the code changes. + +## File structure and naming + +The folder structure mirrors both Gamut's atomic-design tiers and the generated Storybook hierarchy. Find the right folder under `packages/styleguide/src/lib` (`Atoms`, `Molecules`, `Organisms`, and so on), then create a new folder containing `ComponentName.stories.tsx` and `ComponentName.mdx` — plus any example or utility files the stories need. + +- Non-component files with more than one word use a space and sentence case: `General principles.mdx`. +- Component-related files use the component's own `PascalCase` name: `RadialProgress.mdx`. + +## Formatting + +**Numbers and units** + +- Use numerals for all numbers, with commas for thousands (1,000). +- Use standard units — `px`, `rem`, `em`, `%`. +- In prose, put a space between a number and its unit ("16 pixels"); in code, don't ("16px"). + +**Lists** + +- Bulleted lists are for unordered items — keep them in parallel structure, and end each item with a period only if it's a complete sentence. +- Numbered lists are for sequential steps or prioritized items — start each item with a capital letter. + +**Code blocks** + +- Use triple backticks with a language identifier (` ```tsx `, ` ```javascript `, ` ```css `). +- Include comments for complex examples, and keep examples concise and focused. + +**Headings** + +- Start at the second level (`##`) — the first level is set automatically from the page's title. +- Don't skip a heading level; it breaks the reading order. + +**Whitespace** + +- Separate sections with a blank line, and never stack multiple consecutive blank lines. +- Indent code consistently — 2 spaces for TypeScript/TSX, with tabs set to 2 spaces if you use them. + +## Linking + +**Internal links** + +In Storybook's own `.mdx` files, use the `LinkTo` component with an `id` matching the target story's id: + +```tsx +import { LinkTo } from '~styleguide/blocks'; + +Animation; +``` + +- Link text describes the destination, not the action — "See the Stories page," not "Click here." +- Make link text meaningful out of context: "the Stories page," not "click here." +- Link a component's name to its documentation. +- Verify the link actually works. +- Use at least 2–3 words, so the link is easy to click. +- Give each link unique text when more than one appears on the same page. + +**External links** + +Use a plain Markdown link for something like an external tool or reference — most renderers already open these in a new tab: + +```markdown +[GitHub Repository](https://github.com/Codecademy/gamut) +``` + +For more control over the link itself — for example, inside a component that needs an `Anchor` — pass `target="_blank"` together with `rel="noreferrer"` for security, but don't force that behavior unless it's actually needed; a reader can already choose to open a link in a new tab themselves. + +## Referencing code + +**Code in text** + +- Use backticks for inline code: props, CSS properties, component names, prop values (`onClick`, `flex-direction`, `Box`, `true`). +- Use backticks for file and package names too: `Button.tsx`, `package.json`, `@skillsoft/gamut`. +- Refer to a component as "the `Box` component" on first mention, then "the component" afterward. +- Keep a component name singular even when referring to several instances — "these `Box` components," not "these `Boxes`." + +**Code samples** + +Include the necessary imports, use realistic and working examples, add comments for complex logic, keep each example focused on one concept, and use TypeScript types: + +```tsx +import { StrokeButton } from '@skillsoft/gamut'; + +export const SimpleButtonExample: React.FC = () => ( + Click me +); +``` + +**Command-line syntax** + +Use shell (`sh`) syntax highlighting, skip the prompt symbol (`$`), and put one command per block unless several are directly related: + +```bash +yarn add @skillsoft/gamut +``` + +**File paths** + +Use backticks for file paths (`packages/gamut/src/Button/index.tsx`); use a relative path when the context already makes it clear (`./types.ts`), and a workspace-root path when it doesn't. Say "in the `ComponentName.mdx` file" for a code location, rather than a bare path. + +**UI element references** + +- Bold a UI label: **Next**, **Back**, **Close**. +- Describe where an element is: "Click the **Theme Switcher** (paintbrush icon)." +- Use sentence case: "the **Show code** button." +- Prefer device-agnostic language — "click," not a touch- or mouse-specific verb. +- Avoid directional language like "the form on the right" or "the section above" — say "the following form" or "the previous section" instead. From 2230693ba143f62708df6f9d526dcce50fd3e637 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Fri, 2 Oct 2026 14:18:15 -0400 Subject: [PATCH 6/9] updated contrib to gamut doc using starlight changes --- .../docs/guides/contributing-to-gamut.md | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/packages/gamut-docs/src/content/docs/guides/contributing-to-gamut.md b/packages/gamut-docs/src/content/docs/guides/contributing-to-gamut.md index e80d9ef88c..c59f11540d 100644 --- a/packages/gamut-docs/src/content/docs/guides/contributing-to-gamut.md +++ b/packages/gamut-docs/src/content/docs/guides/contributing-to-gamut.md @@ -63,24 +63,15 @@ export type ButtonProps = { Add unit tests in a `__tests__/MyComponent-test.tsx` file within the component's directory, using `setupRtl` from `gamut-tests`. Unit test all component logic, with the exception of class names on components that already contain other tested logic. -## Writing stories +## Writing stories and documentation -Every component needs Storybook stories demonstrating its use, in a `.stories.tsx` file alongside a `.mdx` documentation file. This structure is the source every component page's `StoryEmbed`s pull from, so both files need to stay accurate. File structure and naming conventions live in the [Writing guidance](/guides/writing-guidance/) guide. +A component needs two things: Storybook stories for interactive reference, and a doc page on this site for a reader deciding whether and how to use it. They live in different packages, so a component change usually touches both. -### Writing the `.mdx` documentation file + -A component's `.mdx` file combines its interactive stories with written documentation, usage guidance, and metadata. A good one has four parts: +### Writing the `.stories.tsx` file -1. **General information** — set in the file's `parameters` object: `title` (the component's name, used for linking), `subtitle` (what it does and when to reach for it), `source` (its package and a GitHub link), `design` (a Figma link), and `status`: - - `current` — stable, recommended for use. - - `updating` — in progress; the API may still change. - - `deprecated` — still supported, but slated for removal — don't use it for new work. - - `static` — reference material, with no active development. -2. **Flagship story and props** — a single default story showing the component's baseline state, with `sourceState="shown"` on its `Canvas` so the code is visible, and a connected props table right below it. -3. **Variation stories** — a subsection per meaningful behavior or configuration, each showing one variation with a short description and any variant-specific guidance. -4. **Usage instructions** — when to use the component (and when not to), plus any guidelines a reader should follow. - -### Writing the `.stories.tsx` code file +Add stories in `packages/styleguide/src/lib///ComponentName.stories.tsx`. Storybook builds that story group's docs page automatically from the component's props and JSDoc (`tags: ['autodocs']` in `packages/.storybook/preview.ts`), so a new component needs no Storybook `.mdx` file — accurate [props documentation](#props-documentation) is what drives that page instead. Use concrete, realistic example values instead of placeholders like `foo`/`bar` — a boolean controlling a modal should be named `isModalOpen`, not `isBar`, so the example reads like something a consumer would actually write. @@ -103,9 +94,22 @@ export const Default: Story = { }; ``` +### Writing the component doc page + +A component's written documentation lives on this site, not in Storybook. Add `packages/gamut-docs/src/content/docs/components//.mdx`, kebab-case, under whichever [category](/components/) the component belongs to. See [Using this site](/getting-started/using-this-site/) for the five-part structure every component page follows — Header, Usage, Anatomy, Usage examples, and Playground or Prop Reference. + +A few things specific to writing one of these pages: + +- Frontmatter needs only `title` and `description` — there's no `parameters` object. +- The header is a line of plain text, not a component: `**Status:** · [Figma](figma-url) · [Source](github-url)` — drop the Figma link when the component has no design file. +- Pull in a live Storybook example with `` instead of retyping a prop table or re-describing a variation — Storybook stays the source of truth for that content. Run `yarn nx run storybook:dev` and copy a story's id from the address bar rather than guessing it. +- Embed a Figma frame for the anatomy diagram with ``. + +A page added under a `components/` category folder appears in the sidebar automatically. A page added under `guides/` doesn't — add its slug by hand to the `Guides` section of `packages/gamut-docs/astro.config.ts`. + ### Group overview pages -When a folder holds more than one related component or story, add an `About.mdx` file as its landing page — for example, the Icons folder's `About.mdx` links out to its Mini and Regular sub-pages. Give it a clear overview of what the folder contains and how its components relate, organized by importance or usage frequency, and keep it concise — it's an entry point, not detailed documentation. +When a category folder holds more than one component, its `index.md` is the landing page — give it `sidebar: { label: Overview }` in frontmatter (see `packages/gamut-docs/src/content/docs/components/navigation/index.md` for reference). Give it a clear overview of what the category contains and how its components relate, organized by importance or usage frequency, and keep it concise — it's an entry point, not detailed documentation. ## Pull requests From 2a90c45ebb14a77cfb7c979b4cda2c6242013ea5 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Fri, 2 Oct 2026 15:23:16 -0400 Subject: [PATCH 7/9] renamed writing guide to be an md file and updated sidebar --- packages/gamut-docs/astro.config.mjs | 1 + .../docs/guides/{writing-guidance.mdx => writing-guidance.md} | 0 2 files changed, 1 insertion(+) rename packages/gamut-docs/src/content/docs/guides/{writing-guidance.mdx => writing-guidance.md} (100%) diff --git a/packages/gamut-docs/astro.config.mjs b/packages/gamut-docs/astro.config.mjs index 3b6c01d5e6..5a29b58baf 100644 --- a/packages/gamut-docs/astro.config.mjs +++ b/packages/gamut-docs/astro.config.mjs @@ -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 diff --git a/packages/gamut-docs/src/content/docs/guides/writing-guidance.mdx b/packages/gamut-docs/src/content/docs/guides/writing-guidance.md similarity index 100% rename from packages/gamut-docs/src/content/docs/guides/writing-guidance.mdx rename to packages/gamut-docs/src/content/docs/guides/writing-guidance.md From f9764008a323ba1438c39848910ca8b5fa788f7f Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Fri, 2 Oct 2026 16:25:59 -0400 Subject: [PATCH 8/9] new rationale file --- .../src/content/docs/concepts/index.md | 1 + .../concepts/naming-conventions-rationale.md | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 packages/gamut-docs/src/content/docs/concepts/naming-conventions-rationale.md diff --git a/packages/gamut-docs/src/content/docs/concepts/index.md b/packages/gamut-docs/src/content/docs/concepts/index.md index d65d955b91..fc02130aff 100644 --- a/packages/gamut-docs/src/content/docs/concepts/index.md +++ b/packages/gamut-docs/src/content/docs/concepts/index.md @@ -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/) diff --git a/packages/gamut-docs/src/content/docs/concepts/naming-conventions-rationale.md b/packages/gamut-docs/src/content/docs/concepts/naming-conventions-rationale.md new file mode 100644 index 0000000000..2e282fde78 --- /dev/null +++ b/packages/gamut-docs/src/content/docs/concepts/naming-conventions-rationale.md @@ -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 explain using an example: `List`, `DataGrid`, and `DataTable` all set `disableContainerQuery` to `false` by default, meaning container queries are on for most consumers. But reading that default means resolving a double negative first — "not disabled" — before you know container queries are actually active. + +## Naming the interface, not the implementation + +`smallVariant` or `useSmallStyles` describes how "small" happens to be built today. `size="sm"` describes what the prop controls. If the implementation changes later, a name like `useSmallStyles` becomes actively wrong, while `size="sm"` still means the same thing. It never promised anything about the mechanism. + +## 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. From 2676e40c1daca0e6823c6f58cdb1b50442a847e2 Mon Sep 17 00:00:00 2001 From: Kenny Lin Date: Mon, 5 Oct 2026 13:34:58 -0400 Subject: [PATCH 9/9] more rationale touch-ups --- .../src/content/docs/concepts/naming-conventions-rationale.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/gamut-docs/src/content/docs/concepts/naming-conventions-rationale.md b/packages/gamut-docs/src/content/docs/concepts/naming-conventions-rationale.md index 2e282fde78..e2729eec5d 100644 --- a/packages/gamut-docs/src/content/docs/concepts/naming-conventions-rationale.md +++ b/packages/gamut-docs/src/content/docs/concepts/naming-conventions-rationale.md @@ -21,11 +21,11 @@ Let's explain using an example, if we use props such like `isPrimary` and `isSec 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 explain using an example: `List`, `DataGrid`, and `DataTable` all set `disableContainerQuery` to `false` by default, meaning container queries are on for most consumers. But reading that default means resolving a double negative first — "not disabled" — before you know container queries are actually active. +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 -`smallVariant` or `useSmallStyles` describes how "small" happens to be built today. `size="sm"` describes what the prop controls. If the implementation changes later, a name like `useSmallStyles` becomes actively wrong, while `size="sm"` still means the same thing. It never promised anything about the mechanism. +`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