-
Notifications
You must be signed in to change notification settings - Fork 231
Contributing with AI Assistants
Guide for contributing to Astryx OSS with the help of an AI assistant (Claude, Codex, etc).
Astryx moves fast, but not by skipping steps. Every component change — even a "small" prop addition — goes through a specification protocol that ensures we're solving the right problem the right way. AI assistants are powerful implementation partners, but they can't substitute for the upfront work of understanding usage patterns and design intent.
This guide explains the protocol, where your explorations are most valuable, and how to land contributions successfully.
These areas are great for design-driven contributions and don't require the full spec protocol:
Stories are how people learn the system. Design contributors are uniquely positioned to improve them:
-
Realistic compositions — Show components in context, not just isolated props. A
Tablestory with badges, status dots, and avatar cells teaches more than<Table data={data} />. - Pattern demonstrations — Show how components compose together for common UI patterns (settings pages, dashboards, list-detail views).
- Edge cases — Long text, empty states, loading states, dense layouts. The cases that reveal whether the API actually works.
- Theme coverage — Verify components look right across themes and modes.
Stories use existing component APIs. No package changes needed, no spec protocol required.
The sandbox is a Next.js app for testing components in realistic page layouts. It's the best place to:
- Prototype page templates — Build full page layouts that exercise multiple components together.
- Test compositions — Try combining components in ways the stories don't cover.
- Surface gaps — If you can't build what you need with existing components, that's a valuable signal. File an issue describing the gap (not a PR adding a new prop).
Sandbox pages are local experiments. They can be rough. The goal is learning, not shipping.
Themes are the visual layer of the system. They define what tokens resolve to — colors, radii, spacing scales, shadows, font sizes — without changing component structure or behavior. This separation is intentional: it means designers can tune visuals freely without risking behavioral regressions.
Themes are created with defineTheme() — a plain JavaScript API that takes a flat token map and produces CSS. No StyleX knowledge required, no build plugin needed.
import {defineTheme} from '@astryxdesign/core/theme';
const myTheme = defineTheme({
name: 'my-brand',
tokens: {
'--color-accent': ['#0077B6', '#48CAE4'], // [light, dark] tuple
'--color-surface': ['#F0F8FF', '#0A1628'],
'--radius-container': '16px', // same in both modes
},
components: {
button: { base: { borderRadius: '999px' } }, // component-level overrides
},
icons: myIcons,
});
// Apply it
<Theme theme={myTheme} mode="system">
<App />
</Theme>What you can do in themes:
-
Adjust token values — Make the neutral grey darker, tighten the spacing scale, change the border radius curve. Just override the token in your
defineTheme()call. -
Create new themes — A denser theme for data-heavy tools, a warmer palette for consumer-facing surfaces, a high-contrast accessibility theme. Use
[light, dark]tuples for automatic mode switching. -
Override component styles — Use
componentsto scope visual overrides to specific components (e.g., pill-shaped buttons, thicker card borders) without touching core. -
Pre-compile for production — Run
npx astryx theme build <file>to generate a static CSS file.Themedetects built themes and skips runtime CSS generation.
The rule: themes control what things look like, core controls what things are.
Core components should have the least amount of visual opinion possible. Every visual value — colors, spacing, sizing, line heights, radii — should reference a token, and the theme decides what that token resolves to. This means most visual tuning happens in themes, not core.
| Change | Where | Why |
|---|---|---|
| "This grey should be darker" | Theme — adjust the token value in defineTheme()
|
The component already references the right token; the value just needs tuning |
| "Badge line height feels too tall" | Theme — adjust the line height token | The component should reference a token like --leading-normal, not a raw value. If it does, tune the token. If it doesn't, that's a core fix to add token adherence |
| "Buttons should have rounder corners" | Theme — adjust --radius-md in defineTheme()
|
The component already says borderRadius: --radius-md; what that means is a theme decision |
| "Buttons should be pill-shaped in my app" | Theme — use components: { button: { base: { borderRadius: '999px' } } }
|
Component-level visual overrides belong in the theme |
| "Badge should use a different grey than body text" | Core — change which token the component references | Token selection is a structural decision — it changes the theming contract |
"Add a compact variant to Card" |
Core — new variant requires a new prop | Variants are structural. The theme defines what compact looks like, but core defines that it exists
|
| "This component needs a hover state" | Core — new behavior | Interaction states are behavior, not visual tuning |
Key principle: If you find a raw value in a core component (a hardcoded pixel value, a hex color, a numeric line height), the fix is to replace it with a token reference — not to change it to a different raw value. Core components should be maximally token-dependent so that themes have full control over the visual output.
Lab is a staging area for experimental components that aren't ready for @astryxdesign/core. It's the place to build and iterate on new component ideas without the full quality bar of core.
- Lower bar than core — Components need to work and have basic props, but don't need full accessibility hardening, vibe testing, or spec compliance.
- Breakable — APIs can change freely. Lab is explicitly high-risk. Don't depend on stability.
- Fork to use — Lab components are never published and should not be imported as a dependency. If you want to use one in your app, copy the source into your project. This restriction is what keeps lab safe — if consumers could import from lab, we couldn't freely break APIs without breaking their apps.
- Graduation path — When a lab component is battle-tested and ready, engineering reviews and promotes it to core through the full spec + build protocol.
Lab is great for designers who want to vibe a component idea into existence. Get it working, get it into storybook, let people try it. The hardening comes later.
Note: If your lab update requires updates to core, that will still be vetted with the same strictness as any core change. So for optimal safety, try to keep your changes lab-only.
Storybook, sandbox, themes, and lab don't require changes to packages/core/. No new core props, no new core exports. If your exploration reveals that a core component needs a structural change, that's when you switch to the spec protocol — file an issue describing what you hit.
Building a new component? See Component Lifecycle for the full end-to-end guide covering specification, build, hardening, and ongoing maintenance. This section covers the spec protocol as it applies to all API changes — not just new components.
Any change to component APIs, new components, or behavioral changes follows this protocol. No exceptions — even for "small" prop additions.
Why: A prop that seems simple often has architectural implications. Should padding adjust at the button level or the container level? Should a size change use a token or a raw value? Should a new variant exist on this component or be composed from existing primitives? These questions only have good answers when you've looked at how the component is actually used.
Before writing any code, file an issue that explains:
- What problem are you solving? Not "add X prop" but "users need to do Y and currently can't because Z."
- What's the design intent? What should the experience feel like? Include screenshots, mockups, or references.
- Who needs this? Is this for a specific product surface, or a general pattern?
| ❌ Bad issue | ✅ Good issue |
|---|---|
"Add suiteName prop to TopNavTitle" |
"Product teams need to show app suite context in the top nav. Here's how Workplace, Ads Manager, and Business Suite handle this today: [screenshots]. The current TopNavTitle doesn't support this pattern." |
This is the step that most often gets skipped — and it's the most important one.
Astryx OSS is converging multiple internal design systems and will be the single component library going forward. That means API decisions need to account for usage across multiple codebases:
| Codebase | What it tells you |
|---|---|
| Internal design systems | The original APIs, years of usage data. Which props are actually used at scale, which are dead weight. How the same concepts map across different API surfaces. |
Astryx OSS (@astryxdesign/core) |
The canonical OSS API. What we've already decided, what's missing, what needs to change. Search via GitHub or locally. |
Before proposing a solution, research across these sources:
- Search existing usage. How is this component used today? Which props are actually used, and how frequently? Get real numbers — they often surprise you.
- Check usage frequency. A prop used by 3 callsites has different design constraints than one used by 3,000. Usage data is what separates "this seems useful" from "this is the right API."
- Look at composition patterns. How do people work around the current limitation? Sometimes the workaround reveals the right API better than the original request.
-
Map across systems. Do internal design systems have an equivalent? How do the APIs differ and why? Where do LLMs get confused between them? Checking import counts across internal packages shows where the OSS API is covering real usage and where gaps remain. (Example: one internal system calls its primary CTA variant
default, which causes LLMs to hallucinate aprimaryvariant that doesn't exist — 137 invalid uses found. This kind of insight directly shapes the OSS API.) - Compare with external design systems. How do Radix, Shadcn, Chakra, Ant Design handle this? What can we learn from their API choices?
Your AI assistant can help with all of this — code search, usage analysis, cross-system comparison. Use those skills here, in the issue phase, not later in the PR phase.
When designing Astryx OSS Button variants, research across all three systems revealed:
-
Internal Astryx: 10
typevalues, butprimary+secondary= 97.6% of usage.onMediavariants had near-zero usage. -
Internal alternative: 6
variantvalues.outline+ghost= 88%. The namedefaultfor primary CTA caused widespread LLM confusion. -
Decision: Astryx OSS simplified to 4 variants (
primary,secondary,tertiary,danger) with a clear migration mapping from both systems.
This is the kind of evidence-driven API design the spec protocol produces. Without the cross-codebase research, we'd be guessing.
With research in hand, propose the solution in the issue:
- Show the proposed API with code examples.
- Explain the tradeoffs — why this approach over alternatives.
-
Reference the research — "X has 9,000 usages, 97% use only
primaryandsecondaryvariants, so we can simplify to 3 variants instead of 10."
This is where discussion happens. The maintainers may push back, suggest alternatives, or ask for more research. That's the process working correctly — it's much cheaper to iterate on an issue than on a PR.
Once the API is agreed on in the issue, implementation is the easy part. Your AI assistant is excellent here:
- Write the component code following Astryx conventions (see Component Authoring Guide).
- Always use design tokens — never raw color, spacing, or sizing values.
- All
:hoverstyles must use@media (hover: hover)guards. - Write colocated tests.
- Add Storybook stories.
- Update the component
.doc.mjsdocumentation file. - Open a PR referencing the issue.
When you're choosing between API approaches — different naming, prop vs composition, hook vs context — an API Arbitration run can settle the debate with data instead of opinion.
Build both options, throw the same prompts at them, and see which one an LLM reaches for naturally. This is how Accordion became CollapsibleGroup (discoverability jumped from 3.5/5 to 4.7/5) and how we decided mobile nav state should live on the child, not the parent.
You don't need a vibe test for every change. They're most valuable when the right API isn't obvious and the team is going back and forth. See API Arbitration for the process and sample prompt.
These are patterns from real contributions that didn't land. Understanding them will save you time:
"Increase icon size from 32px to 36px and add icon-only variant"
Closed — filed without context about which products need this or how the component is used today. The change might be right, but without evidence it's a guess. Fix: File an issue with the usage research first.
"Add
isEndContentFlushprop for tighter right padding when end content uses ghost buttons"
Closed. The real question was: should padding adjust at the button level (so all ghost buttons self-compensate) or the container level (adding a prop for every visual edge case)? The answer turned out to be a system-level pattern — but you'd never get there starting from "add a prop." Fix: Describe the problem. Let the spec protocol find the right solution.
"Add
@astryxdesign/dataentity schema package for typed mock data"
Closed. Mock data belongs colocated with the sandbox, not as a published package in packages/. Understanding the project structure matters: packages/ is for shipped code, apps/ is for development tooling. Fix: Check the System Architecture page to understand where things go.
Multiple new components were built as PRs without prior issue discussion. All closed. Even when a matching issue exists, jumping to implementation before the API is agreed on wastes effort for everyone. Fix: Issue first, always. Wait for API agreement before building.
"Fix badge height from 24.5px to 20px" (using raw
lineHeightvalues)
Closed. Swapping one raw value for another doesn't fix the problem — it just changes the hardcoded number. The real fix is to make the component reference a token (e.g., --leading-normal) so the theme controls the visual output. If the badge height needs tuning after that, adjust the token in the theme. Fix: Core components should be maximally token-dependent. Replace raw values with token references; tune the values in the theme.
If you're an AI assistant helping someone contribute to Astryx:
-
Reread this guide periodically. Don't assume you remember it. This document evolves as the team learns what works and what doesn't. At the start of any session involving Astryx contribution, check this page for updates — especially the Common Patterns section, which grows from real closed PRs.
-
Don't skip to code. Your human has design intent that needs to be captured in an issue first. Help them articulate the problem, not just the solution.
-
Do the research. You're great at code search, usage analysis, and cross-system comparison. Use those skills during the issue phase — that's where they create the most value.
-
Respect the safe zones. Storybook stories and sandbox pages are where your human can explore freely. Component API changes require the full spec protocol.
-
Use tokens, not raw values. Every color, spacing, and sizing value should reference a design token. If the token doesn't exist, that's a separate discussion.
-
One focused PR > five speculative ones. A single well-researched contribution that went through the spec protocol will land. Five PRs that skipped it won't.
-
Create PRs in draft mode. Always create PRs as drafts (
gh pr create --draft). Check with your human that the changes are ready before marking it ready for review. Publishing a PR signals to maintainers that it's review-ready — don't waste their attention on work-in-progress. -
Self-review before requesting team review. AI-authored contributions must be reviewed by the contributor before requesting maintainer review. This means:
- Read every line of generated code — don't just check that it runs
- Verify component usage against
npx astryx component <Name> - Confirm tokens, accessibility attributes, and prop values are real (not hallucinated)
- Run the linter and tests locally
- Check that the output matches the design brief, not just the prompt
Review your AI's work before asking others to review it — the same way you'd review your own code before opening a PR.
-
When in doubt, file an issue. Issues are cheap. Closed PRs are expensive — for your human, for you, and for the maintainers reviewing them.
This document is self-healing. When you encounter a pattern that isn't covered here — a new reason a PR got closed, a convention you had to learn the hard way, a research technique that worked well — file an issue on the Astryx repo with the label contributing-guide describing what should be added.
The goal is that every mistake only happens once. If your PR got closed for a reason not listed in Common Patterns, that reason should be added so the next agent doesn't repeat it.
Astryx OSS is a public repository. Everything written here — wiki pages, issues, PR comments, code comments, commit messages, and source files — is visible to anyone on the internet. This means two things: don't name internal systems and don't put internal data in the repo.
When discussing design decisions, usage patterns, or migration context in any public context, keep references general and anonymized:
| ✅ OK | ❌ Not OK |
|---|---|
| "We have ~9,000 usages of this component internally, and 97% use only two variants" | "FooBarSelector in www uses the overlay variant" |
| "Internal apps typically compose this with a sidebar layout" | "The Ads Manager campaign panel does it this way" |
| "Our migration tooling found 137 invalid uses of this pattern" | "Check the internal MultiSelector — it handles sections this way" |
"One internal system names its primary CTA default, which confuses LLMs" |
"InternalLib's XYZComponent has a primary variant bug" |
| "Usage data from internal codebases shows this prop is rarely used" | "Search <internal_path>/components/ for examples" |
This applies everywhere public: PR review comments, issue discussions, wiki pages, code comments, commit messages. A casual reference to an internal component name in a code review is just as much a leak as putting it in a README.
Internal usage statistics, variant analysis, migration tracking data, and adoption metrics must not be committed as files in the repo — even in sandbox or test directories. This includes:
- Usage frequency tables — e.g., "Component X: 4,200 usages, 12 apps, owned by team Y"
- Variant/prop analysis — breakdowns of how internal components are used
- Migration tracking — which internal apps have migrated, which haven't
- Adoption dashboards — internal component counts, team mappings, oncall data
- Cross-system comparisons — side-by-side data from internal design systems
If analysis data is useful for the project, keep it in internal tools (dashboards, docs, pastes). Reference the conclusions in public ("97% of usages use only 2 variants") without committing the underlying dataset.
- General observations about usage volume, patterns, and frequency
- Anonymized descriptions of internal design decisions ("we chose X because Y")
- References to public external systems (Radix, Shadcn, Chakra, MUI, Ant Design, etc.)
- Linking to the Astryx OSS repo itself, its wiki, its issues
- Aggregate numbers without entity names ("across ~50 internal apps", "~3,000 callsites")
This is an open source project. Internal component names, codebase paths, tool names, team structures, and usage data are confidential. They can leak in two ways:
- Naming things — a PR comment that says "the internal FooSelector handles this differently" creates a permanent public record of an internal component name.
- Committing data — a file with variant analysis across internal systems puts confidential usage data into git history permanently, even if the PR is later closed or the file is deleted.
Both are preventable. Describe patterns, not names. Share conclusions, not datasets.
If you're helping someone contribute and you have access to internal codebases for research:
- Do the research internally — search usage, analyze patterns, compare APIs. This is valuable work.
- Present findings publicly as anonymized summaries — "X usages across Y apps, typical pattern is Z"
- Never paste internal file paths, component names, tool names, or codebase identifiers into any public context — not in PR comments, not in code comments, not in commit messages
- Never generate files containing internal analysis data for commit — keep raw data in internal tools, commit only the conclusions
- When in doubt, rephrase — if you're not sure whether something is internal, describe the pattern instead of naming the thing
| What you want to do | Where it goes | Protocol needed? |
|---|---|---|
| Show a component in a realistic layout | apps/storybook/stories/ |
No |
| Build a page template prototype | apps/sandbox/src/app/pages/ |
No |
| Experiment with a new component idea | packages/lab/src/ |
No — lab is a safe exploration zone |
| Adjust a token value (color, radius, spacing) |
defineTheme() in your app or packages/themes/
|
No |
| Create a new theme |
defineTheme() in your app or packages/themes/
|
No |
| Override component styles through a theme |
defineTheme() components option |
No |
| Change which token a component uses | packages/core/src/ |
Issue first — this changes the theming contract |
| Add a new prop or variant | packages/core/src/ |
Yes — full spec protocol |
| Add a new component to core | packages/core/src/ |
Yes — full spec protocol |
| Fix a bug (wrong color, broken layout) | packages/core/src/ |
Issue first, lighter research |
| Update component documentation | Component {Name}.doc.mjs
|
No |