Form Library Comparison Playground — Compare the same forms implemented across different React design systems with full CSS isolation.
This is project that I wanted to implement for quite some time. So far in my professional experience I have worked with a lot of various design systems and I was always fascinated how the same form could look so much differently using different component library.
This is how this project was born. It represents 20 extremely common forms that were built using 41 popular React design systems. Each form is rendered in a fully isolated iframe, preventing any CSS bleed between different design systems.
- 820 Form Implementations — 41 libraries × 20 forms, each running in an isolated context
- CSS Isolation — No style conflicts between design systems (iframe-based architecture)
- Theme Support — Light/dark mode toggle for libraries that support theming
- Comparison Matrix — Side-by-side comparison of forms across libraries
- Static Deployment — Fully static, hosted on GitHub Pages
This project uses a monorepo + iframe architecture for complete CSS isolation:
20forms-20designs/
├── apps/
│ ├── shell/ # Main comparison UI
│ ├── mui/ # MUI (all 20 forms)
│ ├── chakra/ # Chakra UI (all 20 forms)
│ ├── antd/ # Ant Design (all 20 forms)
│ └── ... (42 apps total: 41 libraries + shell)
├── scripts/
│ ├── build-all.mjs # Build orchestration
│ └── copy-builds-to-dist.mjs # Deployment bundler
└── public/ # Static assets
You might wonder: why build 41 separate applications instead of one unified app? The answer comes down to CSS isolation — the core technical challenge of this project.
The Problem with a Single SPA:
When multiple design systems coexist in one application, their styles inevitably conflict:
- MUI's CSS baseline overrides Chakra's default styles
- Tailwind's preflight resets Blueprint's component styling
- Global resets from one library break layouts in another
We initially attempted a single-SPA approach and encountered exactly these issues — forms looked broken, buttons had wrong colors, and spacing was inconsistent across libraries.
The Iframe Solution:
Each <iframe> creates a completely isolated browsing context with its own:
<head>element and stylesheets- CSS cascade and specificity rules
- JavaScript runtime
This means MUI's CssBaseline, Tailwind's preflight, and Chakra's global styles all live in separate worlds — they literally cannot interfere with each other.
Why Not Shadow DOM or CSS Modules?
- Shadow DOM: Doesn't fully isolate CSS custom properties, and many design systems rely on global providers/contexts that don't work well across shadow boundaries
- CSS Modules / Scoped CSS: Only scopes class names, not global resets, CSS variables, or inherited styles that design systems heavily depend on
- CSS-in-JS runtime isolation: Complex to implement across different CSS-in-JS solutions (Emotion, styled-components, Stitches, etc.)
The Trade-off:
Yes, building 41 separate apps means:
- Longer build times (~2-3 minutes for full build)
- Duplicated React/library bundles across apps
- More complex deployment orchestration
But it guarantees:
- Zero CSS bleed between any two design systems
- True visual fidelity — each form looks exactly as it would in a real project using that library
- Independent theming — light/dark mode works correctly per-library without conflicts
- Flexible comparison — users can view any combination of forms and libraries side-by-side
- User registration / sign up
- User login / sign in
- Password reset / forgot password request
- Two-factor authentication code entry
- Contact or support inquiry
- Newsletter or marketing subscription
- Profile information update
- Account security and password change
- Billing information capture
- Shipping address capture
- Checkout with payment details
- Order tracking lookup
- Appointment or booking request
- Event registration / RSVP
- Job application submission
- Customer feedback or satisfaction survey
- Support ticket submission
- Multi-step onboarding wizard
- Advanced search with filters
- Privacy, consent, and communication preferences
All libraries implement the same 20 forms with identical content, labels, and field structures. The React (No CSS) implementation serves as the canonical reference — every other library's forms match its exact structure and text content, differing only in styling and component usage.
| Library | Theme Support | Repository |
|---|---|---|
| React (No CSS) | ✅ Light/Dark | GitHub — Canonical reference |
| # | Library | Theme Support | Repository |
|---|---|---|---|
| 1 | Ant Design | ✅ Light/Dark | GitHub |
| 2 | Arco Design | ✅ Light/Dark | GitHub |
| 3 | Ariakit | ✅ Light/Dark | GitHub |
| 4 | Atlassian Atlaskit | ✅ Light/Dark | Bitbucket |
| 5 | Base Web | ✅ Light/Dark | GitHub |
| 6 | Blueprint | ✅ Light/Dark | GitHub |
| 7 | Braid Design System | ✅ Light/Dark | GitHub |
| 8 | Carbon Design System | ✅ Light/Dark | GitHub |
| 9 | Chakra UI | ✅ Light/Dark | GitHub |
| 10 | Cloudscape | ✅ Light/Dark | GitHub |
| 11 | CoreUI | ✅ Light/Dark | GitHub |
| 12 | DaisyUI | ✅ Light/Dark | GitHub |
| 13 | Elastic UI (EUI) | ✅ Light/Dark | GitHub |
| 14 | Evergreen | GitHub | |
| 15 | Flowbite React | ✅ Light/Dark | GitHub |
| 16 | Fluent UI | ✅ Light/Dark | GitHub |
| 17 | Gravity UI | ✅ Light/Dark | GitHub |
| 18 | Grommet | ✅ Light/Dark | GitHub |
| 19 | Headless UI | ✅ Light/Dark | GitHub |
| 20 | Mantine | ✅ Light/Dark | GitHub |
| 21 | Material Tailwind | ✅ Light/Dark | GitHub |
| 22 | MUI | ✅ Light/Dark | GitHub |
| 23 | PatternFly | ✅ Light/Dark | GitHub |
| 24 | Pinterest Gestalt | ✅ Light/Dark | GitHub |
| 25 | PrimeReact | ✅ Light/Dark | GitHub |
| 26 | Primer React | ✅ Light/Dark | GitHub |
| 27 | Radix UI | ✅ Light/Dark | GitHub |
| 28 | React Bootstrap | ✅ Light/Dark | GitHub |
| 29 | React Spectrum | ✅ Light/Dark | GitHub |
| 30 | RSuite | ✅ Light/Dark | GitHub |
| 31 | Salesforce Lightning Design System | GitHub | |
| 32 | Semantic UI React | ✅ Light/Dark | GitHub |
| 33 | Semi Design | ✅ Light/Dark | GitHub |
| 34 | Shadcn/ui | ✅ Light/Dark | GitHub |
| 35 | Shopify Polaris | ✅ Light/Dark | GitHub |
| 36 | Tamagui | ✅ Light/Dark | GitHub |
| 37 | Theme UI | ✅ Light/Dark | GitHub |
| 38 | U.S. Web Design System | GitHub | |
| 39 | Web Awesome | ✅ Light/Dark | GitHub |
| 40 | Zendesk Garden | ✅ Light/Dark | GitHub |
- Bun (v1.0 or higher) — Install with:
curl -fsSL https://bun.sh/install | bash
Note: This project uses Bun for faster dependency installation and build times. If you prefer Node.js, see the Alternative: Using Node.js section below.
# Clone the repository
git clone https://github.com/evgenyvinnik/20forms-20designs.git
cd 20forms-20designs
# Install dependencies
bun install# Run the shell app in development mode
bun run dev:shellThis runs the shell application in development mode.
# Build all 42 apps (shell + 41 library apps) for GitHub Pages
bun run build
# Preview the production build locally
bun run preview
# Then open: http://localhost:3000/20forms-20designs/The production build uses iframes for complete CSS isolation between libraries.
The main comparison UI that:
- Displays a matrix of forms × libraries
- Renders each combination in an isolated
<iframe> - Provides theme switching (light/dark)
- Allows filtering by form and library
Each library app is a standalone Vite + React application that:
- Contains all 20 form implementations for that design system
- Supports form selection via URL query parameter (
?form=user-login) - Supports theme via URL query parameter (
?theme=dark) - Is completely isolated from other library apps
| Script | Description |
|---|---|
bun run build |
Build shell + all library apps + copy to dist |
bun run build:shell |
Build only the shell app |
bun run clean |
Remove all build artifacts |
bun run dev:shell |
Run shell app in development mode |
bun run preview |
Preview the production build locally |
bun run lint |
Lint all apps (may take a while) |
The shell app includes a comprehensive Playwright test suite to ensure UI functionality works correctly.
cd apps/shell
# Run all tests headlessly
bunx playwright test
# Run tests with Playwright UI (interactive mode)
bunx playwright test --ui
# Run tests with browser visible
bunx playwright test --headed
# Run tests in debug mode
bunx playwright test --debug
# Run tests for a specific browser
bunx playwright test --project=chromium
bunx playwright test --project=firefox
bunx playwright test --project=webkitThe test suite covers:
- Header — Title and subtitle display
- Form Selection — Display, toggle, select all/none functionality
- Library Selection — Display, toggle, select all/none, external links
- Theme Toggle — Light/dark theme switching
- Group By Toggle — Grouping by design system or form name
- Preview Section — Cards, iframes, GitHub links, empty states
- Grouping Behavior — Different section layouts based on grouping mode
- Persistence — localStorage persistence across page reloads
- Dark Theme Badge — Warning for libraries without dark theme support
- Responsive Layout — Two-column and single-column layouts
- External Links — Correct attributes on website/repo links
- Create a new directory:
apps/<library>/ - Implement all 20 forms in
src/forms/following the pattern from existing libraries - Create the App.jsx with form routing via URL parameter
- Update
apps/shell/src/config.tswith the new library (add to LibraryId type, LIBRARY_NAME_TO_ID, LIBRARIES array, and CONSOLIDATED_LIBRARIES set) - Run
bun run build
- Add the new form component to each library's
src/forms/directory - Update the form routing in each library's App.jsx
- Update
apps/shell/src/config.tswith the new form (add to FormId type, FORM_NAME_TO_ID, and FORMS array) - Run
bun run build
This project is configured for automatic deployment to GitHub Pages via GitHub Actions.
- Push to
mainbranch — Triggers the deploy workflow automatically - Manual trigger — Go to Actions → "Deploy to GitHub Pages" → "Run workflow"
The workflow:
- Installs dependencies with Bun (with caching for faster builds)
- Builds all 42 apps (shell + 41 library apps)
- Deploys to GitHub Pages
Live URL: https://<username>.github.io/20forms-20designs/
# Build for production
bun run build
# The dist/ folder contains all static assets
# Upload dist/ to any static hosting providerThe project is configured with /20forms-20designs/ as the base path. If deploying to a different path:
- Update
BASE_PATHinapps/shell/vite.config.ts - Update
buildIframeSrcinapps/shell/src/config.ts - Update base paths in
scripts/generate-mini-apps.mjs
- Bun — Fast JavaScript runtime and package manager
- Vite — Fast build tool and dev server
- React 18 — UI library
- TypeScript — Type-safe shell app
- Bun Workspaces — Fast, disk-efficient monorepo management
- GitHub Actions — CI/CD pipeline
- GitHub Pages — Static hosting
Total non-empty lines of code in the project:
| Extension | Lines |
|---|---|
.jsx |
62,473 |
.md |
36,874 |
.js |
6,742 |
.mjs |
2,484 |
.ts |
1,434 |
.css |
1,213 |
.json |
1,028 |
.html |
986 |
.tsx |
559 |
.scss |
7 |
| Total | 113,800 |
Generated using scripts/calc-sloc.mjs — excludes node_modules, dist, lock files, and other build artifacts.
If you prefer to use Node.js with pnpm instead of Bun:
- Install Node.js 18+ and pnpm 9+
- Install dependencies:
pnpm install - Build:
pnpm run build - Dev server:
pnpm run dev:shell
Note: You'll need to update the package.json scripts and build files to use pnpm and node commands instead of bun.
MIT License — see LICENSE for details.