Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
1a4ae36
feat(web): add the panelui-web package
Khalidabdi1 Oct 11, 2026
cd08742
feat(docs): add a web section with live previews and an App / Web switch
Khalidabdi1 Oct 11, 2026
f3327ac
perf(docs): load a web component's source on demand
Khalidabdi1 Oct 11, 2026
31f5bfe
feat(web): add Planet
Khalidabdi1 Oct 11, 2026
8acc118
feat(web): add Feedback
Khalidabdi1 Oct 11, 2026
da77187
feat(web): export compound parts by name for Server Components
Khalidabdi1 Oct 11, 2026
5fb5edd
feat(web): add Activity Grid
Khalidabdi1 Oct 11, 2026
e144e5c
feat(web): add Range Meter
Khalidabdi1 Oct 11, 2026
7fdd31c
feat(web): add Forecast Bars
Khalidabdi1 Oct 11, 2026
463af31
feat(web): add Stat Strip
Khalidabdi1 Oct 11, 2026
61ba947
feat(web): add Pill Stack
Khalidabdi1 Oct 11, 2026
dbe749e
fix(docs): list Installation once in the web sidebar
Khalidabdi1 Oct 11, 2026
3f8bb08
fix(web): draw Feedback's scrollbar instead of using the browser's
Khalidabdi1 Oct 11, 2026
96a8971
refactor(web): redesign the widgets and Planet in the theme's colour …
Khalidabdi1 Oct 11, 2026
a47b57e
docs(web): put the Day Grid's lead demo in a frame
Khalidabdi1 Oct 11, 2026
e5c0717
feat(web): make Planet's markers pressable and take the glare off the…
Khalidabdi1 Oct 11, 2026
6c38848
refactor(web): rebuild Fold Stack as summaries that open into their card
Khalidabdi1 Oct 11, 2026
ec67071
docs(web): frame the Range Meter and Stat Strip demos and widen Day G…
Khalidabdi1 Oct 11, 2026
782ff7e
feat(docs): show a web component's code under it, behind View Code
Khalidabdi1 Oct 12, 2026
901f9cd
refactor(web): remove Fold Stack
Khalidabdi1 Oct 12, 2026
10253b5
feat(docs): make the web Components page a gallery of running components
Khalidabdi1 Oct 12, 2026
f149f01
feat(web): turn Stat Strip into KPI, with the app KPI's parts
Khalidabdi1 Oct 12, 2026
d724a32
feat(web): let Day Grid run a week per column, and lay two demos out …
Khalidabdi1 Oct 12, 2026
5cdba2a
feat(web): add Lane Board
Khalidabdi1 Oct 12, 2026
edbe7c9
feat(web): add Flow
Khalidabdi1 Oct 12, 2026
9ebae1e
feat(web): add a generated skill, and MCP, Skills, Style and Theme pages
Khalidabdi1 Oct 12, 2026
388dc76
fix(web): draw the Flow lead demo at full size
Khalidabdi1 Oct 12, 2026
6cd494d
feat(web): add Tilt Card and Command Dock
Khalidabdi1 Oct 12, 2026
f7455a2
fix(web): make Command Dock fold away and not snap shut
Khalidabdi1 Oct 12, 2026
de1ea9a
feat(docs): say on the home page that there is a web collection
Khalidabdi1 Oct 12, 2026
261ab9b
fix(web): keep Lane Board steady while a card is dragged and set down
Khalidabdi1 Oct 12, 2026
09b873a
feat(docs): tell the web collection's story in three figures on the h…
Khalidabdi1 Oct 12, 2026
077d22f
ci: add the workflow that publishes panelui-web
Khalidabdi1 Oct 12, 2026
37fae85
feat(web): steady Lane Board's drop target, and add lane colours and …
Khalidabdi1 Oct 12, 2026
14a6a98
fix(docs): wrap the home page's footer links on a phone
Khalidabdi1 Oct 12, 2026
532a2ea
feat(web): give Lane Board a fixed height, with lanes that scroll
Khalidabdi1 Oct 12, 2026
28f3d68
feat(docs): let Flow's previews fill their frame
Khalidabdi1 Oct 12, 2026
f95837c
feat(docs): give the home page's hero a shorter line with a hook
Khalidabdi1 Oct 12, 2026
44bcc4c
feat(docs): make the home page's previews a working, full-width wall
Khalidabdi1 Oct 12, 2026
49ae46e
Merge remote-tracking branch 'origin/main' into web
Khalidabdi1 Oct 13, 2026
6506c09
ci: let a web release find its version already on npm
Khalidabdi1 Oct 13, 2026
e9a9426
fix(docs): make the home page previews' coloured text readable
Khalidabdi1 Oct 13, 2026
50c5654
docs: say in the README that PanelUI is for the web as well
Khalidabdi1 Oct 13, 2026
bfc4348
ci: deploy the site on a web release as well
Khalidabdi1 Oct 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ jobs:
- name: Build library
run: npm run build

# Compiled file by file, then each relative import is completed against
# what was emitted. An import the second half cannot resolve only fails
# here: the typecheck follows the same specifier without its extension.
- name: Build web package
run: npm run build --workspace=panelui-web

# A real production Metro export catches dependency and asset growth that
# source-level package budgets cannot see. Offline mode and a cold cache
# keep this reproducible; the current export takes about 15 seconds.
Expand Down
11 changes: 7 additions & 4 deletions .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Deploys panelui.dev when a library release is published.
# Deploys panelui.dev when a library release or a web release is published.
#
# The docs site used to build on every push and every pull request, through
# Vercel's Git integration. That is a full Next.js build of a site whose content
Expand All @@ -24,8 +24,11 @@
#
# `release: published` fires for every release in the repository, and the CLI
# packages are released under their own tag prefixes. A `cli-v0.4.2` release
# does not change a component page, so it should not spend a build. Only a plain
# `vX.Y.Z` gets through — the same filter publish.yml uses, for the same reason.
# does not change a component page, so it should not spend a build. A plain
# `vX.Y.Z` gets through — the same filter publish.yml uses, for the same reason
# — and so does `web-vX.Y.Z`: the web collection's pages and its registry at
# /r/web are served from this site, so a web release that did not deploy would
# put a new version on npm and leave the old source behind the shadcn CLI.
#
# ## One-time setup
#
Expand Down Expand Up @@ -53,7 +56,7 @@ permissions:
jobs:
deploy:
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' || startsWith(github.ref_name, 'v')
if: github.event_name == 'workflow_dispatch' || startsWith(github.ref_name, 'v') || startsWith(github.ref_name, 'web-v')
steps:
- name: Trigger the production deployment
env:
Expand Down
106 changes: 106 additions & 0 deletions .github/workflows/publish-web.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Publishes panelui-web to npm when a GitHub release is published, in the same
# shape as publish.yml does for the library and publish-cli.yml for the CLIs.
#
# The web collection is versioned independently of all of them, so a release is
# for it only when the tag says so:
#
# web-vX.Y.Z -> packages/web (panelui-web)
#
# A release whose tag does not start with `web-v` publishes nothing here and
# says so, rather than failing: every workflow on `release: published` sees
# every release.
#
# Authentication is npm Trusted Publishing: the job asks GitHub for an OIDC
# token and npm exchanges it for a short-lived one, so there is no credential in
# this repository to leak or rotate, and the package carries a provenance
# attestation naming this workflow and commit.
#
# One-time setup on npmjs.com, without which every run fails at the last step.
# A trusted publisher can only be set on a package that exists, so the first
# version has to be published by hand, once, by somebody with the account's
# second factor. After that:
# panelui-web -> Settings -> Trusted Publisher -> GitHub Actions
# Organization or user: panel-ui
# Repository: PanelUI
# Workflow filename: publish-web.yml
name: Publish web

on:
release:
types: [published]
# A dry run on demand, so the whole path — install, build, test, pack — can
# be exercised without putting anything on the registry.
workflow_dispatch:

permissions:
contents: read
id-token: write # required for trusted publishing

jobs:
publish:
# Decided before anything is installed: a library or CLI release has
# nothing to do here, and should not spend a job finding that out.
if: github.event_name == 'workflow_dispatch' || startsWith(github.ref_name, 'web-v')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
registry-url: https://registry.npmjs.org

# Node 22 ships npm 10, which has no trusted publishing. Without this the
# publish step fails asking for a token that deliberately does not exist.
- name: Use an npm that can publish with OIDC
run: npm install -g npm@latest

- name: Install dependencies
run: npm ci

- name: Typecheck
run: npm run typecheck --workspace=panelui-web

- name: Build
run: npm run build --workspace=panelui-web

# The package contract: every component is exported, client modules say
# so, and every compound part has a name a Server Component can import.
- name: Test the package
run: node --test packages/web/test/*.test.mjs

# The only thing standing between a mistyped tag and a wrong version on
# npm, which cannot be taken back — npm does not allow a version to be
# republished, even after an unpublish.
- name: Check the tag matches the package version
if: github.event_name == 'release'
run: |
tag="${GITHUB_REF_NAME#web-v}"
version="$(node -p "require('./packages/web/package.json').version")"
if [ "$tag" != "$version" ]; then
echo "::error::Release tag ${GITHUB_REF_NAME} does not match packages/web/package.json (${version}). Fix one of them and re-cut the release."
exit 1
fi
echo "Publishing panelui-web ${version} from ${GITHUB_REF_NAME}."

# The first version is published by hand (see the top of this file), and
# its release is cut afterwards so that there is a record of it. That
# run finds the version already on the registry and has nothing to do,
# which is not a failure. npm would refuse it anyway: a version cannot be
# published twice.
- name: Publish
if: github.event_name == 'release'
run: |
version="$(node -p "require('./package.json').version")"
if npm view "panelui-web@${version}" version >/dev/null 2>&1; then
echo "::notice::panelui-web ${version} is already on npm. Nothing to publish."
exit 0
fi
npm publish --provenance --access public
working-directory: packages/web

- name: Dry run
if: github.event_name == 'workflow_dispatch'
run: npm publish --dry-run --access public
working-directory: packages/web
5 changes: 4 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,7 @@ independently of the library and of each other. Which package a release is for c
| `vX.Y.Z` | `packages/panelui` → `panelui-native` |
| `cli-vX.Y.Z` | `packages/cli` → `panelui-cli` |
| `create-vX.Y.Z` | `packages/create-panelui-app` → `create-panelui-app` |
| `web-vX.Y.Z` | `packages/web` → `panelui-web`, by `publish-web.yml` |

`create-panelui-app` depends on `panelui-cli`, so a breaking change to the `panelui-cli/init` or
`panelui-cli/ui` subpath exports needs both released, cli first.
Expand All @@ -633,7 +634,7 @@ counts as that go-ahead for the release they just approved, and for that one onl

### What builds when

Four workflows, and nothing else runs on its own. This is the whole picture:
Five workflows, and nothing else runs on its own. This is the whole picture:

| Event | What runs | What it produces |
| --- | --- | --- |
Expand All @@ -643,6 +644,8 @@ Four workflows, and nothing else runs on its own. This is the whole picture:
| Release published, tag `vX.Y.Z` | `deploy-docs.yml` | panelui.dev, by firing a Vercel deploy hook. |
| Release published, tag `cli-v*` | `publish-cli.yml` | `panelui-cli` on npm. |
| Release published, tag `create-v*` | `publish-cli.yml` | `create-panelui-app` on npm. |
| Release published, tag `web-v*` | `publish-web.yml` | `panelui-web` on npm. |
| Release published, tag `web-v*` | `deploy-docs.yml` | panelui.dev, because the web docs and the registry at `/r/web` are served from it. |
| **Deploy docs** run by hand | `deploy-docs.yml` | panelui.dev, from the current `main`. |

Every workflow on `release: published` sees **every** release and filters by tag prefix itself, so
Expand Down
66 changes: 63 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://shieldcn.dev/header/surface.svg?title=PanelUI&amp;subtitle=High-performance+React+Native+components+for+Expo&amp;logo=https%3A%2F%2Fraw.githubusercontent.com%2Fpanel-ui%2FPanelUI%2Fmain%2F.github%2Fassets%2Flogo-dark.png&amp;mode=dark&amp;align=center&amp;font=geist-mono&amp;border=false" />
<img alt="PanelUI — high-performance React Native components for Expo" src="https://shieldcn.dev/header/surface.svg?title=PanelUI&amp;subtitle=High-performance+React+Native+components+for+Expo&amp;logo=https%3A%2F%2Fraw.githubusercontent.com%2Fpanel-ui%2FPanelUI%2Fmain%2F.github%2Fassets%2Flogo-light.png&amp;mode=light&amp;align=center&amp;font=geist-mono&amp;border=false" />
<source media="(prefers-color-scheme: dark)" srcset="https://shieldcn.dev/header/surface.svg?title=PanelUI&amp;subtitle=High-performance+UI+components+for+Expo+and+the+web&amp;logo=https%3A%2F%2Fraw.githubusercontent.com%2Fpanel-ui%2FPanelUI%2Fmain%2F.github%2Fassets%2Flogo-dark.png&amp;mode=dark&amp;align=center&amp;font=geist-mono&amp;border=false" />
<img alt="PanelUI — high-performance UI components for Expo and the web" src="https://shieldcn.dev/header/surface.svg?title=PanelUI&amp;subtitle=High-performance+UI+components+for+Expo+and+the+web&amp;logo=https%3A%2F%2Fraw.githubusercontent.com%2Fpanel-ui%2FPanelUI%2Fmain%2F.github%2Fassets%2Flogo-light.png&amp;mode=light&amp;align=center&amp;font=geist-mono&amp;border=false" />
</picture>
</p>

Expand All @@ -12,13 +12,15 @@
</p>

<p align="center">
Semantic design tokens · Tailwind v4 via Uniwind · Reanimated on the UI thread.
Semantic design tokens · Tailwind v4 · Reanimated on the UI thread.<br />
React Native for Expo, and now React on the web.
</p>

<p align="center">
<a href="https://panelui.dev/docs"><b>Documentation</b></a> ·
<a href="https://panelui.dev/docs/installation">Installation</a> ·
<a href="https://panelui.dev/docs/components/button">Components</a> ·
<a href="https://panelui.dev/docs/web">Web</a> ·
<a href="https://panelui.dev/docs/customization/theming">Theming</a> ·
<a href="https://panelui.dev/docs/cli">CLI</a>
</p>
Expand Down Expand Up @@ -52,6 +54,10 @@ in one coherent visual language, with light and dark themes out of the box.
Pure TypeScript with no native modules, so it runs in **Expo Go** with no `prebuild`, no Xcode and
no Android Studio.

**New: PanelUI for the web.** [`panelui-web`](#panelui-for-the-web) is a collection of eleven
components for React on the web, in the same visual language and drawn in your project's own
theme: a board of cards you drag, a canvas of nodes and edges, a globe, KPI cards and more.

## Why PanelUI

- ⚡ **Fast styling** — [Uniwind](https://uniwind.dev) brings Tailwind v4 to React
Expand Down Expand Up @@ -173,6 +179,49 @@ Every component takes `className`, so anything can be restyled with Tailwind cla
Plus the primitives: `PanelUIProvider`, `Portal`, `AnimatedPressable`, `useTheme`, `useThemeMode`,
`useToast`, `cn`.

## PanelUI for the web

`panelui-web` is a separate collection for React on the web: Next.js, Vite, React Router, or
anything else that runs React 18 or newer with Tailwind CSS v4. It is not a port of the components
above. It is eleven of its own, styled with Tailwind on the tokens a shadcn project already
defines, so they take your colours and radius in light and dark.

Copy a component's source into your project with the shadcn CLI. It lands in
`components/panelui/` and is yours to edit:

```bash
npx shadcn@latest add https://panelui.dev/r/web/lane-board.json
```

Or install the package, and let Tailwind see the classes it uses:

```bash
npm install panelui-web
```

```css
/* app/globals.css */
@import "tailwindcss";
@source "../node_modules/panelui-web/dist";
```

```tsx
import { RollingNumber } from 'panelui-web/rolling-number';

<RollingNumber value={298} className="text-6xl font-bold" />
```

- **Lane Board** — lanes of cards, dragged with a pointer or carried with the keyboard
- **Flow** — a canvas of nodes joined by edges, to pan, zoom and rearrange
- **Planet** — the world as dots on a globe you can turn, with markers pinned to it
- **Day Grid**, **Bar Picker**, **Range Meter**, **KPI**, **Rolling Number** — figures, to read and
to pick from
- **Command Dock**, **Feedback**, **Tilt Card** — asking, answering and showing

KPI and Flow have the same parts and props as the app library's, so a screen written for one reads
the same in the other. Every component runs live in the docs, with its source under it, at
**[panelui.dev/docs/web](https://panelui.dev/docs/web)**.

## Theming

Six themes ship in [`theme.css`](packages/panelui/theme.css) — `light`, `dark`, `moon`,
Expand Down Expand Up @@ -225,6 +274,17 @@ The skill activates on its own once a project has `panelui-native` in its depend
`panelui.json` beside it. Full details, and the URLs everything is served from, at
**[panelui.dev/docs/skills](https://panelui.dev/docs/skills)**.

The web collection has a skill of its own, generated the same way from its source:

```sh
npx skills add panel-ui/PanelUI --skill panelui-web
```

Its components are shadcn registry items, so the shadcn MCP server can search and add them once
the registry is named in `components.json`. Both are covered at
**[panelui.dev/docs/web/skills](https://panelui.dev/docs/web/skills)** and
**[panelui.dev/docs/web/mcp](https://panelui.dev/docs/web/mcp)**.

## Example app

An Expo Router showcase with a live demo for every component and a theme picker — used to
Expand Down
Loading
Loading