AGENTS.md · styled-components/styled-components
AGENTS.md from styled-components/styled-components: 157 lines covering CSS surface, Basics, Critical Constraints, Mandates and more.
Template by styled-components
Install
Works with Codex, Cursor, GitHub Copilot, Gemini CLI, OpenCode, Claude Code. Read the repository before you give any tool access to your data.
curl -fsSL https://raw.githubusercontent.com/styled-components/styled-components/main/AGENTS.md -o AGENTS.md
Read it and adapt it to your project before use.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: prompt-engineering
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
What's in it
An AGENTS.md file: instructions coding agents (Codex, Cursor, Copilot, Gemini CLI and others) read before working in a repo.
- For
- Codex, Cursor, GitHub Copilot, Gemini CLI, OpenCode, Claude Code
- Length
- 157 lines
- File
AGENTS.md
Sections
- CSS surface
- Basics
- Critical Constraints
- Mandates
- Method
- Security posture
- Agent Rules
- Doc placement
- Key Commands
- Build Architecture
- CSS polyfills
- native-showcase rules
- Topical references
First lines (shown under the repo's MIT license)
This file is the repo's only agent instructions file; there is no CLAUDE.md. It holds one-line rules that prevent a recurring defect class (each naming its guardrail or depth doc), everyday commands, and a map of every doc. Everything else lives in a named home in this repo: a spec under `docs/`, a check's header, or a source comment. Never narration, history, backlog, drifting figures, searchable code trivia, or a second copy of a fact that has a home. A line found out of place moves home in the change that finds it, leaving the rule and a pointer.
## CSS surface
- styled-components supports only standards-compliant CSS in authored styles.
- The native layer exists to map idiomatic CSS to equivalent platform primitives and polyfill what is possible until React Native catches up.
- Do not add authored property names or spellings that are not real CSS (no inventing kebab-case aliases for React Native-only props). Standard properties and values only; platform-specific RN style keys belong in `style={{}}` or upstream RN CSS support, not in the template DSL.
## Basics
- When a task touches consumer-facing behavior, read the most recent llms.txt at https://styled-components.com/llms.txt for the current consumer-facing feature summary
- GitHub Issues is the backlog: file a discovered deficiency or deferred work there, never in this file
- Use pnpm package manager and associated commands. `packageManager` in `package.json` pins the version; install settings live in `pnpm-workspace.yaml`, never `.npmrc`. Installs link from pnpm's global virtual store, so each git worktree installs in seconds. A package that imports another it does not declare resolves only through `packageExtensions` there; `pnpm check:type-resolution` catches the type-level cases, and `scripts/metro-workspace.cjs` holds the Metro-side resolution for the React Native apps
- Formatting is Biome (config in `biome.jsonc`), enforced by a lint-staged pre-commit hook; `pnpm format` runs it repo-wide (fine to do). Match the config rather than hand-formatting. Biome has no Markdown or YAML formatter yet, so those files are left alone; keep them tidy by hand. Suppress a formatting decision with `// biome-ignore format: <reason>` (spaced, not hyphenated; the hyphenated spelling is a suppression parse error), never `// prettier-ignore`, which Biome does not honor. The linter is on with a curated rule set plus two GritQL plugins; see the Critical Constraints below and the comments in `biome.jsonc`.
- Use conventional commits: `(feat|fix|chore|refactor|test|docs|style|perf|build|ci): [description]`
- Default to short commit messages: title only, or title plus one tight sentence
- Ask for consent before every git push; one push's approval never carries to the next. Never `git stash`; use a temp commit. Update the lockfile whenever dependencies change and confirm the new version works.
- Fetch origin before starting work or rebasing. Prefer fast-forward, fall back to rebase; no merge commits.
- Dependency install scripts stay off. Allow one package's scripts only on demonstrated breakage, with a one-line reason beside the allowlist entry.
- Prose is plain: a reader without the domain understands it. Define a term of art at first use or use the plain word.
- No "Generated by" or attribution footer on commits, PRs, changesets, or any output.
- American English for prose: comments, docs, test titles, and user-visible strings (e.g. color, behavior, honor, recognize, serialize). Keep original spelling inside verbatim quotes from specs or standards.
## Critical Constraints
- Strive for perfection in the lightest and quickest package. A change proves its cost against the prior code before landing: shipped bundle bytes, emitted output bytes (CSS, HTML, server-component payload), and render time, measured before and after; a regression in any needs a stated reason
- NEVER use `precedence` or `href` on `<style>` elements, and NEVER dedup RSC style tags across instances with a request-scoped ledger; both lose styles under Suspense or flip the cascade. RSC style tags are plain inline `<style>`, one per instance. See [docs/react.md](docs/react.md) "RSC style emission and Suspense".
- The native entry must NEVER transitively import DOM code via value imports. Use `import type` and branded `Symbol.for()` checks instead of `instanceof`. Verify: `grep -c 'document\.' native/dist/styled-components.native.js` must be 0. RN/Hermes 0.85+ fails at module evaluation time on `document` references.
- Server detection draws on three independent signals, not a universal union. `__SERVER__` is the build-time constant set true in the server build. `IS_RSC` is true at runtime in React 19 server components because the `react-server` export condition serves a build without `createContext` (Next.js/Turbopack apply this condition automatically). `styleSheet.server` is the per-request flag set by `ServerStyleSheet`. Each gate picks the minimum subset of signals that catches the scenarios it cares about: pure SSR with `ServerStyleSheet`, server build under Jest jsdom (DOM mutations still happen so `__SERVER__` alone is misleading), a sheet forced into server mode under a build where `__SERVER__` is false (#3551; this does not make the browser build work on a server, whose tags are always DOM tags), and RSC server component (no `createContext` so React context hooks would crash). The trap: any gate that suppresses DOM work purely on `__SERVER__` will break the jsdom test path; gate on `IS_RSC || styleSheet.server` for that decision instead.
- `React.useRef` is `undefined` in RSC server components. Gate behind `!__SERVER__ && !IS_RSC` (a build constant and a load-time constant, so a bundle takes the branch always or never), never use `typeof React.useRef === 'function'` (runtime conditional hook).
- NEVER place a hook on one side of the render cache's hit/miss branch in `StyledComponent.ts`. The hook sequence must be identical whether or not props changed, and that branch is data-dependent, unlike `__SERVER__` / `IS_RSC` which are build or load-time constants and therefore safe to branch hooks on. A `use`-prefixed helper that calls no hooks is the trap that makes such a call look safe; don't introduce one. Two gates: `correctness/useHookAtTopLevel` in Biome, and `memoization.test.tsx`, which compares recorded hook sequences across a cache-hit and a cache-miss pass and so catches what the syntactic rule cannot.
- The hook rule cannot tell a build-time constant from a data-dependent condition, so every legitimate branch carries a `biome-ignore lint/correctness/useHookAtTopLevel:` naming the constant that makes it stable (`biome-ignore-start`/`-end` for a whole block behind one gate). Adding one is a claim that the condition cannot change between renders of a given component. If it depends on props, state or context, it is the bug the rule exists to find; fix it instead.
- This library performs no work in a passive or layout effect. `useEffect` and `useLayoutEffect` are banned outside tests by the `no-effects` GritQL plugin plus `noRestrictedImports`, which together cover the `React.useX`, bare, and named-import spellings. The replacements, in order of preference: a ref callback for anything needing a committed host or an unmount teardown, `useSyncExternalStore` for external mutable state, and `useInsertionEffect` for stylesheet writes only. A site with no non-effect form carries a `biome-ignore lint/plugin/no-effects:` naming what was tried and why it failed, measured rather than asserted.
- `useInsertionEffect` is still an effect and is restricted by a second plugin to the files that write to the stylesheet. React documents it for that one job. A state update cannot be scheduled from one (a reconciler guard errors on the attempt, including one that wakes another component through a store notify) and refs are not attached when the hook runs, so `useInsertionEffect` is not a general effect replacement.
- Build every composed ref with `useComposedRef` in `native/composeRef.ts`, never a hand-rolled `useMemo` over a ref callback. Ref-callback identity is load-bearing; the hook's JSDoc holds its identity rules. Pass `[]` as `keys` where the teardown is unmount-only, and the value where a change must re-arm, such as an anchor name that has to release the old name. Guarded by the nesting and caller-identity tests in `commit-order.test.tsx`.
- A ref-based teardown only runs if the composed target forwards `ref`, which is the target's half of the contract rather than something the library can work around. Where the teardown removes module-global state, gate registration itself on the ref having attached so both halves share one fate, and warn in dev naming what the target must do. An ungated register plus an unreachable deregister is a leak nothing can clean up.
- `src/native/test/commit-order.test.tsx` banks the commit-order baseline: a flat log interleaving registry writes with a consumer's render, insertion, layout, passive and ref phases. The arrays are hand-written, never snapshots. Moving work between phases changes them, and the diff has to be adjudicated in the same commit rather than regenerated.
- In-house CSS parser (v7) is the SOLE CSS emission path. When changing CSS emission, extend `emit-web.test.ts`, WPT-derived parser tests under `src/parser/wpt-corpus/test/`, or other parser/compiler suites you touch.
- React Native responsive runtime (`src/native/responsive.ts`): never re-resolve `Dimensions` / `Appearance` / `AccessibilityInfo` / `PixelRatio` inside subscription callbacks. The module-level slot via `getRN()` is intentional; the cached references survive Jest teardown so subsequent renders still find the APIs.
- `hoistNonReactStatics` MUST never copy React identity markers (`$$typeof`/`$$id`/`$$async`/`$$bound`) from a target. Without that block, `styled(ClientComponent)` from RSC silently drops every extension; the wrapper inherits the client-reference identity and React skips its render body. See #5672.
## Mandates
- React 19+ compat (v7 peer floor)
- `strict` is on and types are law: no `as any`, non-null `!`, or `@ts-expect-error` to get past the checker. If a type won't express, change the code's shape rather than bypass it. The one allowed `@ts-expect-error` is a negative-behavior assertion in a type test, and it must read clearly as that assertion. Resolve escape hatches in code you touch, not only code you add. Biome's `noNonNullAssertion` and `noExplicitAny` enforce this as errors in library source; a genuinely required `any` carries a `biome-ignore` quoting the compiler error `unknown` produced. Test, bench, and profiling code is exempt for now.
- Backward compatibility is required only on public API surfaces (exported components, props, public-facing data shapes). Internal code is free to rewrite for emergent complexity.
- Strict TDD is enforced: extend or add failing tests that encode the desired contract before changing production code; implement only enough to turn them green. Prove the red with an unfiltered run of the whole test file: a `-t` filter skips the earlier tests that warm module-level caches, so a test can fail alone and pass in the suite. No user-visible behavior change ships without tests that would fail without it.
- Tests cover real logic, happy paths, edge cases, and failure modes, never how something looks (looks are judged by rendering and viewing). Never snapshot CSS or class strings to assert presentation; that is behavior-free brittleness, the exception being a unit whose job is computing that string. Prefer full-output or inline-snapshot assertions, and normalize volatile non-contractual regions (generated ids, stylesheet bytes, timestamps) to stable placeholders so a non-behavioral tweak never forces a test edit.
- Read a file's existing tests before the first edit to it; extend an existing suite before creating one. Derive each expected value from the contract, never by copying observed output. Treat snapshot drift as a suspected regression until ruled out.
- Branch coverage stays above 80% (no gate enforces it; `PULL_REQUEST=1 pnpm --filter styled-components test:web` prints it). Local suites run fast (well under 30s); debug a slow suite rather than accept it.
- Hot paths: the happy path never pays for an edge case; rare-shape handling runs behind a cheap presence check. Microbenchmark-validated patterns and V8 gotchas are in [docs/performance.md](docs/performance.md).
- Always microbenchmark to validate optimizations. Bench the realistic workload, not a synthetic best case. Revert changes that pessimize the path actual callers take, even if they win in isolation.
- Optimize for low memory pressure and monomorphic functions
- Tests AND implementation for CSS surfaces are always grounded in the current editor's draft on `drafts.csswg.org`. Before adding or materially changing a test or polyfill, re-fetch the relevant module section into `/tmp/` for the session (`curl -s https://drafts.csswg.org/<module>/ > /tmp/<module>.html`) and quote the normative clause verbatim above the test that locks it. Never paraphrase from training data; never substitute MDN or TR for the editor's draft. If the spec is silent or ambiguous, document the deviation next to the test and link the CSSWG issue. This applies to every CSS-touching change, not just polyfills. The exceptions: the WPT-derived corpus under `src/parser/wpt-corpus/`, whose source is web-platform-tests, and regression suites, which follow the lighter spec-cite rule in [docs/css-polyfills.md](docs/css-polyfills.md).
## Method
- Fix at the cause, not the symptom. A change that hides a symptom without removing what produced it is a suppression, and they read alike however different they look: a governor over a slow effect, an `as any` over a type that won't check, a snapshot updated to green, a disabled lint rule, a swallowing `try`/`catch`, a sleep or retry over a race, a `|| ''` over an undefined. Where the cause is genuinely out of reach, name the suppression as one and file it (GitHub Issues is the backlog) rather than working around it silently.
- Before calling work done, argue the case that it is wrong: hunt the input that breaks it, the unsupported claim, the skipped requirement, and fix what that surfaces.
- Ground claims in fresh evidence: re-read the file, re-run the command, re-render, rather than trusting recall or a summary. Distinguish what you observed (cite file:line or command output) from what you inferred.
- A reported defect names a class. Before judging one instance, search for every instance (code, tests, callers, docs, config) and fix them all in the same change, or list the ones left and why.
- Verify the primitive before layering a workaround on it, and ask what a check returns when the check itself fails: prefer output shapes where an absence is visible over ones that look complete either way.
- Before deleting code, state the user-visible behavior it produces and confirm the replacement covers that exact behavior. After deleting a module, search for what it leaves behind: still-called handlers, CI steps, comments, and docs whose producer is gone (knip sees only unreferenced symbols).
- One fact, one home. A value that can be derived is derived at its point of use. After changing a behavior, name, or value, grep the repo for every place that recorded the old one (docs, tests, comments, CI, this file) and update them in the same session.
## Security posture
- Authored template CSS is developer-authored; the library does not sanitize it on the author's behalf. Interpolated `${...}` values can carry runtime data, so three structural guards stop an untrusted value from escaping its context: interpolation-slot realization in `parser/compile.ts` (a slot cannot introduce new declarations or rules), the SSR style-body escaping in `utils/escapeStyleSink.ts` (a value cannot close the `<style>` tag), and React's own DOM-attribute escaping for everything routed through `createElement`. Preserve all three when touching those paths; each has a regression test.
- The in-house parser and the native transform regexes run on user-influenced input, so they must stay linear-time. Never write a regex that can backtrack catastrophically: no `\s` inside a quantified class flanked by outer `\s*`, no lazy `[^X]+?` with a trailing `\s*`, no adjacent greedy-then-lazy `[^{}]` runs across a delimiter. Prefer `indexOf` plus anchored single-pass scanning over nested quantifiers, and give any hot-path regex a wall-clock-budget regression test.
## Agent Rules
- Use block comments (`/** */`) on exported members, functions, types, and fields so editors surface them as hover docs; block form is fine for in-body notes too. Comments stay minimal and explain the non-obvious "why" of the CURRENT code, never narrating an edit or how the code used to be. Do not restate CSS spec citations (`§n.n`, drafts.csswg.org URLs, "per CSS Foo 4 §x") inside production source or warnOnce messages; section anchors, normative quotes, and rationale belong in the test that locks the behavior. Warn messages name the offending construct and a concrete, actionable fix; no implementation details.
- Keep type, interface, and object-literal fields alphabetized unless the order is load-bearing (mirrors a wire or positional order); comment why when it is. Apply this to code you write and nearby code you already edit, not as a standalone reformat.
- Reuse an exported constant over a repeated string literal. A function taking four or more arguments takes an options object for the optional ones.
- A non-test source file past ~1,000 lines prompts a split question in the change that grows it ("no, one cohesive unit" is a valid answer).
- A public-facing change always carries a changeset (bug fixes, features, breaking changes). A new feature or breaking change gets its own entry; amending a related unreleased changeset is not a substitute. Skip only internal refactors, build tooling, tests, and fixes to code introduced within the same unreleased version.
- Changesets and PR descriptions are consumer-facing: say only what is and is not supported, in words a user who hasn't read the source understands. State the change's own boundary (what now works, and what within its scope still does not); link the native support matrix at https://styled-components.com/docs/compatibility for the full picture rather than cataloging unrelated gaps. No internal API names, mechanism, or code paths; those live in the commit body and the repo-root `docs/` specs.
- Describe behavior as it currently is, in every doc, comment, changeset, and PR text. No journey narration ("previously", "no longer", "now", "replaces", "instead of", "rather than") for states no released version shipped; fold a fix on a still-unreleased feature into that feature's changeset. Dropping something a released version did ship is a real transition the reader has to act on: announce it as "has been removed in favor of `X`", never "is gone".
- Do not edit CHANGELOG.md, it is auto-generated by changesets
- Public-facing docs (README, `packages/*/README.md`, `packages/styled-components/docs/*`, FAQ, sandbox README) describe user-observable behavior only. Implementation details belong in the repo-root `docs/` specs unless the answer to a user question specifically requires them.
- Use the full family of punctuation marks when writing comments and prose, avoid em-dashes.
- Links to the site use the bare domain (`https://styled-components.com`), never `www`.
- After editing `packages/styled-components/src/utils/errors.md`, run `pnpm run generateErrors` before tests. Jest snapshots compare against the compiled error map and fail on stale error text otherwise; commit the regenerated `src/utils/errors.ts` with the change. Delete any stray `errors.js` if error-text snapshots start failing; Jest's module resolver prefers `.js` over `.ts`.
- The canonical error catalog is `packages/styled-components/src/utils/errors.md`, the only one `generateErrors` reads. The repo-root `src/utils/errors.md` is a frozen pre-monorepo stub (it self-declares "DO NOT EDIT") that nothing consumes; leave it alone.
- Don't name specific AI providers in contributor-facing docs. "An AI coding assistant" is the neutral phrasing.
- Don't hard-wrap prose in PR bodies, issue bodies, or GitHub comments. Single-line paragraphs with blank lines between them, markdown re-flows automatically and hard wraps look broken in mobile viewers and quoted replies. Commit message bodies may use the 72-char convention.
## Doc placement
- Repo-root `docs/*.md` files are the contributor specs. They capture earned knowledge and pitfall avoidance that don't fit naturally into source comments. They are NOT mechanism walkthroughs of single features; the source is authoritative for mechanism.
- Source-level "why" lives in code comments / JSDoc next to the code it describes. The `compileNative.ts`, `source.ts`, `WebGlobalStyle.ts`, `native/animation/index.ts` modules already carry detailed comments; rely on those rather than restating them here.
- CSS normative text: do not paste editor's-draft (or TR) clauses into consumer-facing README, `packages/styled-components/docs/*`, or the repo-root `docs/` specs. Link to the relevant `drafts.csswg.org` (or TR) section and summarize user-observable behavior. Verbatim spec quotes for locking behavior belong next to the test assertions, not in prose docs, so citations stay current and grepable.
## Key Commands
- `pnpm build`: Build main package; its `prebuild` type-checks and its `postbuild` runs `test:build` (bundle and tree-shaking assertions against `dist`)
- `pnpm test`: Run the web, native, and type suites (not `test:build`, benches, or the `verify`-only checks)
- `pnpm --filter sandbox dev`: Start Next.js dev server
- `pnpm --filter styled-components test:web`: Run the web suite against source under jsdom
- `pnpm --filter styled-components test:native`: Run the React Native suite against source
- `pnpm --filter styled-components bench`: `bench:web` then `bench:rsc`
- `pnpm --filter styled-components bench:web`: Every suite under `src/bench/` matched by `jest.config.bench.js` (`*.bench.test.*`, `web*.test.*`, `preprocess*.test.*`). Each file's header docblock states what it measures, so a new suite joins the run by matching one of those patterns. Do not enumerate the suites here.
- `pnpm --filter styled-components bench:web:stress`: Stress benchmarks only (`src/bench/web.test.js`); uses `SC_BENCH_ITER_SCALE=0.2` and `SC_BENCH_RUNS=3` for quicker runs
- `pnpm --filter styled-components bench:rsc`: `src/bench/rsc.test.tsx`, matched by `jest.config.bench-rsc.js` (node environment, `IS_RSC` server path)
- `pnpm --filter styled-components type-perf`: Consumer type-check budget, measured against the built `dist` (needs `pnpm build` first). `--against <package root>` measures another copy of the package with the same fixture. See [docs/typescript-performance.md](docs/typescript-performance.md)
- `pnpm knip`: Dead-code scan (unused files, dependencies, exports); config in `knip.jsonc`. Output is a candidate list, not a delete list
- `pnpm verify`: The local gate; the steps and their order are in `scripts/verify.mjs`
- `pnpm check:type-resolution`: Traces TypeScript resolution over the library and sandbox tsconfigs and fails when a package in pnpm's global store imports a type package it does not declare (those types would silently become `any`). Fix with an optional peer under `packageExtensions`, or a reasoned entry in the script's `KNOWN_FALSE_POSITIVES`. Exit 2 means the check saw nothing and is broken
- Native render perf: use `packages/ios-benchmark` (real Hermes V1 on iOS sim). `react-test-renderer` 19.2 with the RN preset doesn't synchronously invoke function components, and V8 numbers don't predict Hermes, so no in-tree native render bench exists. Algorithm-shape benches (parser, responsive, RSC) run via `bench:web` / `bench:rsc`.
- CPU profiling with Bun, and on-device animation debugging: [docs/profiling.md](docs/profiling.md). Debug channels log one line per event, never host instances or props objects.
## Build Architecture
- `__SERVER__`: build-time constant, `true` in the server build only. See the server-detection bullet in Critical Constraints for which gates depend on it and which traps to avoid.
- `__NATIVE__`: build-time constant, `true` ONLY in the native build. The jest native setup flips it to `true` so jest-side createTheme tests reach the native branch.
- `__NATIVE_WEB__`: build-time constant, `true` ONLY in the rn-web variant of the native bundle. Gate browser-handles-natively passthroughs (`light-dark`, `oklch` / `lab` / `lch` / `color-mix`, viewport units, `calc()` static-mixed) and host-only fixes (matrix3d rewrite) on this constant so rollup tree-shakes per bundle. Jest defaults the global to `false`; flip per-test to exercise the rn-web branch.
- `__DEV__`: build-time constant gating dev-only code. Substitution is wired so consumer bundlers DCE the dead branch in production via the standard `NODE_ENV` path. Prefer the outermost applicable `if (__DEV__) { ... }` that can wrap a contiguous dev-only region; never rely only on `warnOnce` or helpers to skip work. Message strings and warning arguments must not be constructed outside `__DEV__`. Jest defaults the global to `true`; flip per-test to exercise the production branch.
- `IS_BROWSER` (`src/constants.ts`; window and document both present) is a runtime check; bundlers CANNOT tree-shake code behind it, so gate a decision that is fixed per bundle on the matching `__*` constant instead. See [docs/bundling.md](docs/bundling.md).
- `IS_RSC`: module-level constant defined in `utils/isRsc.ts`. Rollup replaces it with literal `false` in every build except the server one, for DCE; runtime semantics live in the server-detection Critical Constraint above.
- `browser` field is preferred over `exports` for mapping server bundles to browser variants: `exports` caused TS2742 in composite projects, and Jest-jsdom and Metro web send identical conditions yet resolve different files today, so any map moves one of them. Server rendering on the edge runtime is unsupported and reports errors 19/20 instead. See [docs/bundling.md](docs/bundling.md).
- Subpath entries (`styled-components/native`, `styled-components/plugins`) use a physical `<subpath>/package.json`. No `exports` field. Add the subpath directory to the top-level package.json `files` array so it's published. First-party plugins and the plugin types (`SCPlugin`, `DeclResult`, `DeclTransform`, `SelectorTransform`) export only from `styled-components/plugins`, never the package root.
- The `styled-components/native` subpath selects its engine per platform with Metro platform-extension files, NOT a `browser` object-map. `main` is the extensionless base `./dist/styled-components`; Metro resolves `styled-components.native.js` (Hermes engine) natively and `styled-components.web.js` / `styled-components.js` (rn-web bridge) on web. `module` points at the bridge ESM (`styled-components.web.esm.js`) for tree-shaking web bundlers. Do NOT reintroduce a `browser` map here: Metro applies `browser` on every platform, not just web, so a map silently routes the rn-web bridge onto iOS/Android (pulling in react-native-web and styleq). The `.js` fallback is the bridge, never the Hermes engine, so a web-side fallback never lands on native code. Locked by `src/test/treeshake.test.ts` → "native subpath platform-extension entries".
- `__VERSION__`: build-time constant holding the package version string; every substitution site (rollup, the Jest globals, the showcase's babel config, the Bun profiling globals) must define it alongside the constants above.
- Browser builds always use CSSOM injection; consumers wanting CSS as text call `extractCSS()`.
- CSS injection ordering: group IDs are allocated at call time, lower ID emits earlier in the stylesheet.
## CSS polyfills
- Every native CSS polyfill has a spec-driven validation block, with an `on rn-web` sub-describe, before its internals are refactored. Follow the procedure and the rn-web parity rules in [docs/css-polyfills.md](docs/css-polyfills.md).
- An unsupported native construct emits `warnOnce` with a stable `code` and drops the declaration; never ship a guess to React Native.
- Keep `packages/styled-components/docs/rn-css-compatibility.md` current with every change to native CSS support.
## native-showcase rules
The `packages/native-showcase` app is the visual QA surface for v7 native polyfills across iOS, Android, and rn-web. It is also a fairness contract: it must not lie about what works on which platform. Rules apply to every widget and every piece of chrome in the showcase.
- Omniplatform components only. No `Platform.OS` / `Platform.select` branches in showcase code, and no `__NATIVE_WEB__` reads outside the library. Same JSX runs everywhere. Platform-skew workarounds belong inside `packages/styled-components` (transforms, polyfills, passthrough, dev warnings); never in the app.
- No raw DOM tags. The showcase has no `<select>`, `<a>`, `<input>`, `<table>`, `<dialog>`, etc., even web-gated. Compose from RN primitives (`View` / `Text` / `Pressable` / `Modal` / `FlatList` / `TextInput`) or pull in a cross-platform community lib.
- Maximize visual obviousness. A working vs not-working state must be glanceable at arm's length. Use macro differences: layout shifts, position changes, color floods, glyph layout, presence/absence; not subtle hue swings or 1-pixel tweaks. The reader should never have to squint.
- Both light and dark mode must work for every widget. Default to `light-dark()` for any color decision so theme switches repaint without React re-renders. If a widget hardcodes a color, that color must read in both modes; if it can't, route through `light-dark()` or `useColorScheme()`.
- Zero tolerance for fake demos. Every widget exercises the actual library/runtime path it claims to demonstrate. If a polyfill can't run on a given platform, the cell is allowed to fail loudly (broken layout, warnOnce message, missing glyph); that failure is the demo on that platform. Never substitute a JS-computed mock that mimics the spec, and never hardcode `t.colors.pass`/`fail` as a stand-in for the polyfill firing. Before adding a new widget, verify how the surrounding widgets prove their behavior and follow the same pattern.
## Topical references
- TypeScript type-instantiation budget, measured type constraints, and type-surface testing: [docs/typescript-performance.md](docs/typescript-performance.md)
- React 19 behaviors that hit the render hot path and SSR/RSC output: [docs/react.md](docs/react.md)
- React Native substrate: CSS capability by RN version, Yoga divergences, the in-house declaration transform: [docs/react-native.md](docs/react-native.md)
- Jest + jsdom: jsdom's modern-CSS ceiling and how the web suite reads injected CSS: [docs/jest.md](docs/jest.md)
- Bundling: per-bundle build-constant substitution, terser DCE traps, testing tree-shaking, dts/TS2742, edge runtime, published files: [docs/bundling.md](docs/bundling.md)
- Web rendering sequence diagram: [docs/rendering-flow.md](docs/rendering-flow.md) (web flow only)
- Animation adapters + spec coverage (Hermes / reanimated; rn-web hands CSS animation to the browser): [docs/animation-adapters.md](docs/animation-adapters.md)
- Native CSS polyfill procedure and rn-web parity: [docs/css-polyfills.md](docs/css-polyfills.md)
- `attrs` precedence and its escape hatches: [docs/attrs.md](docs/attrs.md)
- Microbenchmark-validated hot-path patterns and V8 gotchas: [docs/performance.md](docs/performance.md)
- Bun CPU profiling and on-device animation debugging: [docs/profiling.md](docs/profiling.md)Signals
- Stars
- 41,061
- Forks
- 2,613
- Open issues
- 17
- Last commit (this folder)
- Oct 19, 2026 (11 days ago)
- Last release
- Oct 12, 2026 (17 days ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
Cite this page
AGENTS.md · styled-components/styled-components. Agent Mart. https://agentmart.dev/templates/styled-components-styled-components-agents-md (last updated 2026-10-24)