A minimal starter kit for Web Components projects built with Lit, Vite, and TypeScript.
- Web Components with Lit — reactive properties, scoped styles, Shadow DOM
- Vite 8 dev server + Rolldown-powered production builds
- TypeScript with strict mode
- Oxlint + Oxfmt — Rust-speed lint and format, no ESLint or Prettier
- Vitest Browser Mode — real Chromium tests with full Shadow DOM support
- Built-in
URLPattern-based router — zero external dependencies, History API, declarative<router-outlet>
Scaffold a new project with a single command:
bunx @mrbrunowolff/minimal-web-components my-appThe interactive CLI will:
- Ask for your project name (or use the one you provided)
- Let you choose between bun or npm
- Clone the template, set up git, and install dependencies
git clone https://github.com/MrBrunoWolff/minimal-web-components.git my-app
cd my-app
rm -rf .git bin
git init && git add . && git commit -m "Initial commit"
bun install# Start the dev server at http://localhost:3000
bun run dev
# Type-check + production build → dist/
bun run build
# Preview the production build
bun run previewTests run in real Chromium via Playwright — no JSDOM, full Shadow DOM support.
bun testExample test lives in test/app-root.test.ts. Add tests alongside your components in test/.
bun run lint # Oxlint
bun run lint:fix # Auto-fix
bun run fmt # Oxfmt (write)
bun run fmt:check # Oxfmt (check only)
bun run check # lint + fmt:check togetherminimal-web-components/
├── bin/
│ └── minimal-web-components.js # CLI scaffolder (npx/bunx)
├── src/
│ ├── components/
│ │ ├── app-root.ts # Shell — header, nav, footer, router setup
│ │ ├── page-home.ts # Home page component
│ │ └── page-about.ts # About page component
│ ├── router/
│ │ ├── index.ts # Router class (URLPattern + History API)
│ │ └── router-outlet.ts # <router-outlet> Lit element
│ ├── styles/
│ │ └── global.css # CSS reset + custom properties (light/dark)
│ └── main.ts # Entry point
├── test/
│ └── app-root.test.ts # Vitest Browser Mode test
├── index.html
├── package.json
├── tsconfig.json
├── vite.config.ts
├── vitest.config.ts
└── .oxlintrc.json
Routes are configured in src/components/app-root.ts:
const router = new Router([
{ path: '/', component: 'page-home' },
{ path: '/about', component: 'page-about' },
{ path: '/users/:id', component: 'page-user' },
]);The <router-outlet> element renders the matched component. Navigate with standard <a href="..."> links — the router intercepts clicks automatically. For programmatic navigation:
import { router } from '../router/index.js';
router.navigate('/about');The matched component receives route params via the route-change custom event on window. Listen for it in your component:
window.addEventListener('route-change', (e) => {
const { params } = (e as CustomEvent).detail;
console.log(params.id); // e.g. '42' for /users/42
});This template is intentionally minimal. Common additions:
- More pages — add a new
src/components/page-*.tsand register it inapp-root.ts - Global state — add
@lit/contextfor component-tree-scoped state - Async tasks — use
@lit/taskfor async data fetching - CSS framework — drop a stylesheet in
src/styles/and import it inmain.ts - SSR — add
@lit-labs/ssrwhen you need server-side rendering
MIT