Thanks for taking the time to contribute. This document covers how the repository is laid out, what a change is expected to include, and how to get a pull request merged.
By participating you agree to abide by the Code of Conduct.
- Bug fix? Go ahead — open a bug report and send the fix.
- New component, or a change to an existing API? Open an issue or a discussion first. PanelUI aims at one coherent visual language across the full component catalogue, and the design conversation is much cheaper before the code than after it.
- Documentation or a typo? Straight to a pull request.
Requires Node 20+ and npm. The repo is an npm-workspaces monorepo, so install once at the root.
git clone https://github.com/panel-ui/PanelUI.git
cd PanelUI
npm install| Command | What it does |
|---|---|
npm run example |
Start the Expo showcase app |
npm test |
Discover and run every repository contract test |
npm run typecheck |
Typecheck every workspace |
npm run build |
Build the library with react-native-builder-bob |
npm run performance:example |
Production-export the Android showcase and check its bundle budget |
npm run docs |
Start the documentation site |
npm run docs:generate --workspace=docs |
Regenerate the docs pages and the CLI registry |
The example and docs workspaces resolve panelui-native directly from packages/panelui/src.
That keeps local typechecks and development on the current source even when the ignored lib/
publish output is missing or was built from an older commit. Run npm run build when validating
the package that will be published; a normal workspace typecheck does not require it.
packages/panelui the library — pure TypeScript, no native code (npm: panelui-native)
apps/example Expo Router showcase, one live demo per component
apps/docs documentation site and landing page (panelui.dev)
These are not style preferences; the build enforces most of them.
One folder per component. packages/panelui/src/components/<name>/index.tsx, exported from
src/index.ts — the component and every public type, in the alphabetical position.
Animations run on the UI thread. Reanimated 4 only. The React Native Animated API is not
used anywhere in this library and should not be introduced.
Variants are computed once. tv() objects live at module scope, never inside render. A
variant belongs to the part it configures — if an option only applies to Foo.Item, express it as
a lookup rather than a tv() variant on the root, or the docs generator will document it as
something you can pass to <Foo>.
No hardcoded colours. Everything resolves through the semantic tokens in
packages/panelui/theme.css. A dynamic colour comes from useCSSVariable, with a literal only as
a fallback.
Accessibility is part of the component. Interactive parts wire up accessibilityRole, mirror
their state through accessibilityState, and label anything that is not self-describing.
Forward consumer props before props the component owns. A later JSX spread can silently
replace internal handlers, disabled state, roles, state, or layout. Compound controls with these
boundaries belong in scripts/owned-props.json; npm test parses their TSX and requires every
inventoried prop after the consumer spread. Compose consumer callbacks explicitly when both must
run rather than surrendering the internal boundary.
Every component takes className. It is how a consumer restyles anything.
Overlays mount lazily through Portal and unmount after their exit animation.
Compound components are assembled with Object.assign — Card.Header, Dialog.Content.
- A bare string is only legal inside a
<Text>. Dropping one into a<View>throws "Text strings must be rendered within a<Text>component". ThetextChildrenhelper insrc/primitives/text.tsxwraps bare strings for you; most compound parts already use it. - A
<View>inside a<Text>breaks the line around it. Anything meant to sit inline — a bolded run, inline code — has to be a<Text>.
A component change is not complete until its documentation page is updated in the same commit.
The MDX pages are generated. Do not edit apps/docs/content/docs/** by hand — the next
regeneration will overwrite it. Edit these two files instead:
apps/docs/scripts/meta.json— the component's name, summary, search keyword, and options:group,addedIn(the version it first ships in),updatedIn(the version its API last changed in), andalpha/beta.apps/docs/scripts/usage.json— intro, usage snippet, anatomy, parts, examples and notes. Seeapps/docs/scripts/README.mdfor what each key becomes.
Then run:
npm run docs:generate --workspace=docsProps tables are read from the actual TypeScript interfaces and their JSDoc, so the way to fix a wrong props table is to fix the JSDoc.
The same command rebuilds the CLI registry under apps/docs/public/r, which is generated from the
library source and never hand-written. Two consequences:
- A new relative import must be resolvable by the builder or it throws. Import from
../../primitives,../../utils/cn,../../icons,../../native,../<component>or../hooks/<name>; anything else needs a mapping added toapps/docs/scripts/build-registry.mjsfirst. - A new npm dependency is picked up automatically. Decide whether it is required or optional —
optional means reached through a lazy
require/importinside atry/catch, and listed inOPTIONALin the builder.
packages/panelui/src/components/<name>/index.tsx- Export it and its types from
packages/panelui/src/index.ts - New icons →
packages/panelui/src/icons/index.tsx - New tokens →
packages/panelui/theme.cssandapps/docs/app/global.css, kept in sync - A demo entry in
apps/example/src/data/components.tsx apps/docs/scripts/meta.jsonandusage.json, thennpm run docs:generate --workspace=docs- The component lists and counts in
README.md,packages/panelui/README.mdandapps/docs/content/docs/index.mdx
A demo that needs the whole screen — a transcript, a map, a long answer — is marked
fullPage: true with an id and a description, so it opens on a route of its own instead of
being squeezed into a section.
Conventional Commits, scoped with the component or area where it helps:
feat(table): frame the table and make sorting read as working
fix(calendar): clamp navigation to its bounds
docs(readme): list the AI components
feat, fix, docs, refactor, chore, test, perf, build, ci.
Keep a pull request to one logical change. Before you open it:
npm run typecheck # must pass
npm test # must pass; focused `.only` tests are rejected
npm run build # must passCI runs both on every pull request. Fill in the pull request template — the checklist is short and it is what the review is against.
Do not bump the version in packages/panelui/package.json in a pull request; releases are cut
separately.
Please do not open a public issue. Use the repository's Security tab → Report a vulnerability, which is private and visible only to the maintainers.
By contributing you agree that your contributions will be licensed under the MIT Licence.