A minimalist, well-structured React + Lit hybrid project built with Vite, TypeScript, and modern web standards. Showcases seamless integration of React components and Lit web components following clean architecture principles.
- React 19.2.0 - Latest React with modern hooks and concurrent features
- TypeScript 5.9.3 - Type safety and modern JavaScript features
- Vite 7.1.10 - Lightning-fast dev server and build tool
- Node.js 25.1.0 - Latest LTS Node.js runtime
- Yarn Berry 4.10.3 - Modern package management with node_modules
- Lit 3.3.1 - Web Components with Shadow DOM isolation
- @lit-labs/react - Seamless Lit-React interop
- Path Aliases - Clean imports using
@components,@utils, etc. - Vitest 3.2.4 - Modern testing framework with coverage
- Testing Library - React testing best practices
- ESLint 9 - Modern flat config with TypeScript-aware linting
- Prettier 3.6.2 - Consistent code formatting
- Structured Logger - Production-ready logging system
- Environment Configuration - Flexible configuration via
.envfiles - PWA Support - Optional Progressive Web App capabilities
- Overview
- Getting Started
- Scripts
- Lit Web Components
- Testing
- PWA / Offline
- Project Structure
- Path Aliases
- Environment Variables
- Logger
- Environment Types & Editor Support
- Notes on Ports
- License
This repository showcases a hybrid component architecture combining React 19 and Lit 3 web components, built with the latest Vite and Yarn 4.
It demonstrates best practices for integrating framework-specific React components with framework-agnostic Lit web components in a single application.
The project is optimized for modern development:
- β‘ Fast dev server powered by Vite.
- π§© Hybrid architecture: React + Lit components working seamlessly together.
- π¦ Modern dependency management with Yarn 4.
- π Reproducible installs via
yarn.lock.
- Node.js: 24+ (LTS works too)
- Yarn: 4.9.4+
yarn installCopy .env.example to .env and configure your environment:
cp .env.example .envyarn devRuns the Vite dev server (http://localhost:5173).
yarn buildBuilds the production bundle into /dist.
yarn previewServes the /dist folder locally (http://localhost:4173).
When using this template as a starter for your new project, follow this checklist:
# Edit package.json and update:
- "name": "your-project-name"
- "description": "Your project description"
- "version": "1.0.0"
- "author": "Your Name <your.email@example.com>"
- "homepage": "https://github.com/yourusername/your-project"
- "repository.url": "git+https://github.com/yourusername/your-project.git"
- "bugs.url": "https://github.com/yourusername/your-project/issues"
- "keywords": ["your", "keywords", "here"]<!-- Edit index.html: -->
<title>Your Project Name</title>
<meta name="description" content="Your project description for SEO" />
<meta name="apple-mobile-web-app-title" content="YourApp" />// Edit src/App.tsx:
<h1>Your Project Name</h1>
<p>Your tagline or description</p>VitePWA({
manifest: {
name: "Your Full App Name", // Full name
short_name: "YourApp", // Short name (12 chars max)
description: "Your app description", // Add description
theme_color: "#your-color", // Your brand color
background_color: "#ffffff",
// Update when you have your own icons
},
});# Create and replace these files in public/:
public/
βββ favicon.ico # 16x16, 32x32, 48x48 (your favicon)
βββ pwa-192x192.png # 192x192 (Android home screen)
βββ pwa-512x512.png # 512x512 (Android splash screen)
βββ (optional) apple-touch-icon.png # 180x180 (iOS)
# Replace sample image:
src/assets/images/
βββ sample.png # Replace with your actual imagesIcon Generation Tools:
- Favicon.io - Generate favicon from text/image
- RealFaviconGenerator - Comprehensive favicon generator
- PWA Asset Generator - CLI tool for PWA icons
<!-- Edit index.html: -->
<meta name="theme-color" content="#your-light-color" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#your-dark-color" media="(prefers-color-scheme: dark)" />/* Edit src/index.css - update color variables */
:root {
--primary-color: #your-color;
--accent-color: #your-accent;
}# Edit public/robots.txt when deploying:
User-agent: *
Allow: /
# Add your production sitemap:
Sitemap: https://yourdomain.com/sitemap.xml# Update .env and .env.example:
VITE_APP_NAME=Your App Name
VITE_API_URL=https://api.yourdomain.com
VITE_LOG_LEVEL=info
VITE_LOG_TIMESTAMP=true# Update remote repository:
git remote set-url origin https://github.com/yourusername/your-project.git
# Update LICENSE file if needed
# Update README.md with your project detailsIf you don't need the example components:
# Remove example components:
rm src/components/SimpleGreeting.ts
rm src/components/ReactCounter.tsx
rm src/components/ReactCounter.module.css
rm src/components/LitComponents.tsx
# Update src/components/index.ts
# Update src/App.tsx to remove component usage# Remove sample assets if not needed:
rm src/assets/images/sample.png
# Remove example tests:
rm tests/App.test.tsx
# Create your own components and testsAfter customization, verify all systems:
# Install dependencies
yarn install
# Run type checking
yarn type-check
# Run linting
yarn lint:check
# Run formatting check
yarn format:check
# Run tests
yarn test:run
# Build for production
yarn build
# Preview production build
yarn preview- Updated
package.json(name, description, author, repository) - Updated
index.html(title, description, app title) - Updated
src/App.tsx(project name and description) - Updated
vite.config.tsPWA manifest (name, short_name, theme) - Replaced
public/favicon.icowith your favicon - Replaced
public/pwa-192x192.pngwith your icon - Replaced
public/pwa-512x512.pngwith your icon - Updated theme colors in
index.html - Updated
public/robots.txtwith your domain - Updated
.envand.env.examplewith your variables - Updated git remote URL
- Removed example components (if not needed)
- Removed sample assets (if not needed)
- Ran all verification commands
- Updated this README with your project documentation
| Script | Description |
|---|---|
yarn dev |
Start development server with hot reload |
yarn build |
Build production bundle to /dist |
yarn preview |
Preview production build locally |
yarn type-check |
Check TypeScript types without compilation |
yarn lint |
Lint and auto-fix code with ESLint |
yarn lint:check |
Check for linting issues without fixing (CI/CD) |
yarn lint:fix |
Lint and force fix all auto-fixable issues |
yarn format |
Format code with Prettier |
yarn format:check |
Check if code is formatted correctly (CI/CD) |
yarn test |
Run tests in watch mode (Vitest) |
yarn test:run |
Run tests once (CI-friendly) |
yarn test:watch |
Run tests in watch mode with UI |
yarn test:coverage |
Run tests with coverage report |
yarn test:ci |
Run tests with coverage and verbose output |
This project integrates Lit web components with React using @lit-labs/react, providing true Shadow DOM isolation and framework-agnostic components.
- Framework Agnostic: Lit components work in any framework or vanilla JS
- Shadow DOM Isolation: Styles are scoped and don't leak
- Small Bundle Size: Lit is lightweight (~5KB minified + gzipped)
- Web Standards: Built on native Custom Elements API
- Type Safety: Full TypeScript support with decorators
- Interoperability: Seamless React integration via @lit-labs/react
A basic greeting component demonstrating property binding and styling:
import { SimpleGreeting } from "@components";
function MyApp() {
return <SimpleGreeting name='Developer' />;
}Features:
- Shadow DOM with scoped styles
- Property binding with
@propertydecorator - Gradient background with custom styling
An interactive counter demonstrating state management and custom events:
import { SimpleCounter } from "@components";
function MyApp() {
const [count, setCount] = useState(0);
const handleCountChanged = (event: Event) => {
const customEvent = event as CustomEvent<{ count: number }>;
setCount(customEvent.detail.count);
};
return <SimpleCounter count={count} onCountChanged={handleCountChanged} />;
}Features:
- Interactive buttons (increment, decrement, reset)
- Custom events with
count-changedevent - React state synchronization
- Styled with yellow gradient theme
- Create the Lit component in
src/components/:
// MyComponent.ts
import { LitElement, css, html } from "lit";
import { customElement, property } from "lit/decorators.js";
@customElement("my-component")
export class MyComponent extends LitElement {
static styles = css`
:host {
display: block;
padding: 1rem;
}
`;
@property({ type: String })
message = "Hello";
render() {
return html`<p>${this.message}</p>`;
}
}
declare global {
interface HTMLElementTagNameMap {
"my-component": MyComponent;
}
}- Create React wrapper in
src/components/LitComponents.tsx:
import { createComponent } from "@lit/react";
import React from "react";
import { MyComponent as MyComponentWC } from "./MyComponent.js";
export const MyComponent = createComponent({
tagName: "my-component",
elementClass: MyComponentWC,
react: React,
events: {
// Map custom events to React props
onMyEvent: "my-event",
},
});- Export from index in
src/components/index.ts:
export { MyComponent } from "./LitComponents";
export type { MyComponent as MyComponentElement } from "./MyComponent";Lit decorators require specific TypeScript settings (already configured):
{
"compilerOptions": {
"experimentalDecorators": true,
"useDefineForClassFields": false
}
}- Use Shadow DOM: Leverage Lit's encapsulation for style isolation
- Type Events: Define custom event types for better React integration
- Arrow Functions: Use arrow functions for event handlers to avoid
thisbinding issues - Small Components: Keep components focused and composable
- Test in Browser: Lit components work best tested in actual browser environments
This project uses Vitest and Testing Library for comprehensive test coverage.
- Test Runner: Vitest 3.2.4 with jsdom environment
- DOM Matchers: @testing-library/jest-dom
- Test Location:
tests/directory mirrorssrc/structure - Setup File:
tests/setupTests.ts - Coverage Provider: v8 (fast native coverage)
- Coverage Thresholds: 80% for branches, functions, lines, and statements
The following files are excluded from coverage reports:
- Entry points:
src/main.tsx,src/pwa.ts - Type definitions:
src/env.d.ts,src/vite-env.d.ts,src/**/*.d.ts - Test files:
tests/**,src/**/*.{test,spec}.{ts,tsx} - Type-only files:
src/types/** - Index files:
src/**/index.{ts,tsx}
# Run tests in watch mode
yarn test
# Run tests once (CI-friendly)
yarn test:run
# Run tests in watch mode with UI
yarn test:watch
# Generate coverage report
yarn test:coverage
# Run tests with coverage and verbose output (for CI)
yarn test:ciTests follow React Testing Library best practices:
import { render, screen } from "@testing-library/react";
import MyComponent from "../src/components/MyComponent";
test("renders component correctly", () => {
render(<MyComponent />);
const element = screen.getByRole("button", { name: /click me/i });
expect(element).toBeInTheDocument();
});This project includes Progressive Web App (PWA) support with comprehensive mobile optimization for both iOS and Android.
- Service Worker - Offline functionality via Workbox
- Web App Manifest - Install prompt and app metadata
- Icons - Multiple sizes for different platforms
- Theme Colors - Adaptive browser chrome coloring
- Mobile Optimization - iOS and Android specific enhancements
favicon.ico- Standard browser faviconpwa-192x192.png- PWA icon for mobile devicespwa-512x512.png- PWA icon for desktop/large displaysrobots.txt- Search engine crawling configuration
- β Add to Home Screen with custom icon
- β Status bar styling in standalone mode
- β Custom app title: "React+Lit"
- β Theme color integration
- β Install prompt banner
- β Custom themed browser chrome
- β Auto-generated splash screen
- β Standalone app mode
- β Theme color (light/dark mode adaptive)
The PWA manifest is configured in vite.config.ts:
VitePWA({
registerType: "autoUpdate",
manifest: {
name: "Minimal React Project",
short_name: "MinimalReact",
icons: [
{ src: "/pwa-192x192.png", sizes: "192x192", type: "image/png" },
{ src: "/pwa-512x512.png", sizes: "512x512", type: "image/png" },
],
},
});Mobile-specific meta tags are configured in index.html:
<!-- Theme colors (adaptive to light/dark mode) -->
<meta name="theme-color" content="#646cff" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#535bf2" media="(prefers-color-scheme: dark)" />
<!-- iOS Safari -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="React+Lit" />
<link rel="apple-touch-icon" href="/pwa-192x192.png" />Enable the service worker by importing in src/main.tsx:
import "./pwa";For offline testing, use a production build:
yarn build && yarn previewOpen DevTools β Application β Service Workers, then Update / Skip waiting to apply the latest SW.
- iOS: Use Safari on iPhone, tap Share β Add to Home Screen
- Android: Open in Chrome, look for "Add to Home Screen" banner
- Desktop: Chrome will show an install icon in the address bar
minimal-react-project/
βββ index.html # Vite entry HTML with mobile meta tags
βββ public/
β βββ favicon.ico # Browser favicon
β βββ pwa-192x192.png # PWA icon (mobile)
β βββ pwa-512x512.png # PWA icon (desktop)
β βββ robots.txt # Search engine configuration
βββ src/
β βββ App.tsx # Main app component
β βββ main.tsx # Application entry point
β βββ index.css # Global styles
β βββ pwa.ts # Service worker registration
β βββ assets/
β β βββ images/
β β βββ sample.png
β βββ components/
β β βββ SimpleGreeting.ts # Lit web component
β β βββ ReactCounter.tsx # Pure React component
β β βββ ReactCounter.module.css # CSS Modules for React
β β βββ LitComponents.tsx # React wrappers for Lit
β β βββ index.ts # Barrel exports
β βββ context/ (.gitkeep)
β βββ hooks/ (.gitkeep)
β βββ pages/ (.gitkeep)
β βββ services/ (.gitkeep)
β βββ styles/ (.gitkeep)
β βββ types/ (.gitkeep)
β βββ utils/
β βββ logger.ts
β βββ logger.types.ts
β βββ index.ts # Barrel exports
βββ tests/
β βββ App.test.tsx
β βββ setupTests.ts
βββ tsconfig.json
βββ vite.config.ts
βββ node_modules/ # Dependencies installed here
Configured in both tsconfig.json and vite.config.ts:
@components/*βsrc/components/*@utils/*βsrc/utils/*- plus:
@context,@hooks,@pages,@services,@styles,@types
Example:
import Button from "@components/Button";
import { formatDate } from "@utils/date";Environment variables are configured in .env file (copy from .env.example).
Build-time (Vite config only):
NODE_ENV- Environment mode (development/production)PORT- Dev server port (default: 5173)
Runtime (Browser - VITE_ prefix required):
VITE_LOG_LEVEL- Logging level (error/info/debug)VITE_LOG_TIMESTAMP- Enable timestamps in logs (true/false)
Vite Built-in (always available in browser):
import.meta.env.MODE- Current mode (development/production)import.meta.env.DEV- Boolean, true in developmentimport.meta.env.PROD- Boolean, true in productionimport.meta.env.BASE_URL- Base URL for the app
// Direct access to environment variables
import.meta.env.MODE // 'development' or 'production'
import.meta.env.DEV // true/false
import.meta.env.VITE_LOG_LEVEL // 'debug', 'info', etc.
// Using the logger (configured from env variables)
import { logger } from "@utils/logger";
function MyComponent() {
logger.info('Component mounted');
logger.debug('Debug information', { userId: 123 });
logger.error('Something went wrong', { error: 'details' });
// Create child logger with prefix
const childLogger = logger.child('MyComponent');
childLogger.info('This will be prefixed');
return <div>Hello</div>;
}Note: Only variables prefixed with VITE_ are exposed to browser code. Variables like NODE_ENV and PORT are only available in vite.config.ts.
This project uses ESLint 9 with flat config for code quality and best practices.
# Run ESLint and auto-fix issues
yarn lint
# Check for linting issues without fixing (useful for CI/CD)
yarn lint:check
# Force fix all auto-fixable issues
yarn lint:fixESLint Configuration:
- TypeScript-aware linting with type checking
- React Hooks rules enforcement
- React Refresh rules for HMR compatibility
- JSX accessibility rules (a11y)
- Automatic import sorting
- Vitest-specific rules for test files
Configuration file: eslint.config.mjs
Key Plugins:
eslint-plugin-react-hooks- React Hooks lintingeslint-plugin-react-refresh- Fast Refresh compatibilityeslint-plugin-jsx-a11y- Accessibility ruleseslint-plugin-simple-import-sort- Automatic import organizationeslint-plugin-vitest- Vitest-specific linting
Code formatting is handled by Prettier (separate from linting).
# Format all files
yarn format
# Check if files are formatted correctly (useful for CI/CD)
yarn format:checkPrettier Configuration:
- 150 character line length
- 2 spaces indentation
- Semicolons enabled
- Single quotes for JSX
- ES5 trailing commas
Configuration file: .prettierrc.json
Excluded paths:
dist/,build/,coverage/node_modules/.yarn/cache directories.envfiles
Recommended VS Code extensions:
- ESLint (
dbaeumer.vscode-eslint) - Linting support - Prettier (
esbenp.prettier-vscode) - Formatting support
Your .vscode/settings.json is already configured for:
- β Format on save
- β Auto-fix ESLint issues on save
- β Consistent line endings (LF)
- β TypeScript project references
- β nvm support for correct Node.js version
Add to your CI pipeline:
# Example GitHub Actions
- name: Check Formatting
run: yarn format:check
- name: Lint Code
run: yarn lint:check
- name: Type Check
run: yarn type-check
- name: Run Tests
run: yarn test:ci
- name: Build
run: yarn buildUse VS Code to debug your React application and tests.
Application Debugging:
- Debug React App - Launch Chrome with dev server (requires
yarn devrunning first)
Test Debugging:
- Debug Vitest Tests - Debug all tests with breakpoints
- Debug Current Test File - Debug single test file
Debug React Application:
- Run
yarn devin a terminal - Wait for the server to start (you'll see "Local: http://localhost:5173")
- Press
F5or select "Debug React App" from the debug menu - Set breakpoints in your React code and they'll hit when you interact with the app
Debug Tests:
- Open a test file or any file
- Press
F5and select "Debug Vitest Tests" or "Debug Current Test File" - Set breakpoints in your tests or source code
- Tests will run with debugger attached
VS Code Keyboard Shortcuts:
| Action | Shortcut |
|---|---|
| Start Debugging | F5 |
| Stop Debugging | Shift+F5 |
| Toggle Breakpoint | F9 |
| Step Over | F10 |
| Step Into | F11 |
| Step Out | Shift+F11 |
A structured logger is included that automatically configures itself from environment variables.
- Log Levels: ERROR, INFO, DEBUG (configurable via
VITE_LOG_LEVEL) - Timestamps: Optional timestamps (controlled by
VITE_LOG_TIMESTAMP) - Child Loggers: Create prefixed loggers for different modules
- Metadata: Attach JSON objects to log entries
import { logger } from "@utils/logger";
// Basic logging
logger.error("Error message", { error: "details" });
logger.info("Info message");
logger.debug("Debug info", { data: { foo: "bar" } });
// Child logger with prefix
const componentLogger = logger.child("MyComponent");
componentLogger.info("Component initialized");Logger is configured from environment variables:
VITE_LOG_LEVEL:error,info, ordebug(default:debugin dev,infoin prod)VITE_LOG_TIMESTAMP:trueorfalse(default:true)
- Global types:
tsconfig.jsonincludes"types": ["vite/client", "vitest/globals", "node"]. - Test setup:
tests/setupTests.tsimports@testing-library/jest-dom. - VS Code: Standard TypeScript integration with node_modules.
- Dev:
yarn devβ http://localhost:5173 - Preview:
yarn previewβ http://localhost:4173
(Override invite.config.tsviaserver.port/preview.portor setPORTin.env).
- Node.js:
>=25.1.0(Latest LTS) - Yarn:
>=4.10.3(Berry with node_modules)
If you need to manage multiple Node.js versions:
# Using nvm (Node Version Manager)
nvm install 25.1.0
nvm use 25.1.0
# Using fnm (Fast Node Manager)
fnm install 25.1.0
fnm use 25.1.0Note: This project includes a .nvmrc file that specifies Node.js 25.1.0. If you use nvm, it will automatically use the correct version when you cd into the project directory (with nvm auto-use enabled).
- react
^19.2.0- React library - react-dom
^19.2.0- React DOM renderer - lit
^3.3.1- Web Components library - workbox-window
^7.3.0- Service worker runtime (PWA)
- vite
^7.1.10- Build tool and dev server - typescript
^5.9.3- TypeScript compiler - @vitejs/plugin-react
^5.0.4- React Fast Refresh with Babel (supports decorators) - lit
^3.3.1- Web Components library - @lit-labs/react
^2.1.3- Lit-React interop wrapper - vitest
^3.2.4- Test framework - @vitest/coverage-v8
3.2.4- Coverage provider - @testing-library/react
^16.3.0- React testing utilities - @testing-library/jest-dom
^6.9.1- Custom jest matchers - eslint
^9.37.0- Linting (flat config) - typescript-eslint
^8.46.1- TypeScript ESLint support - prettier
^3.6.2- Code formatting - vite-plugin-pwa
^1.1.0- PWA support - jsdom
^27.0.0- DOM implementation for testing
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Follow React and TypeScript best practices
- Run linting:
yarn lintbefore committing - Format code:
yarn formatto ensure consistent style - Type check:
yarn type-checkto verify TypeScript types - Write tests for new components
- Update documentation
- Use conventional commits
- Ensure build passes
Before submitting a PR, ensure:
- Code passes linting (
yarn lint:check) - Code is formatted (
yarn format:check) - Types are valid (
yarn type-check) - Tests pass (
yarn test:ci) - Coverage thresholds met (80%)
- Documentation is updated
- Build succeeds (
yarn build)
This project is licensed under the MIT License - see the LICENSE file for details.
For support and questions:
- Create an issue in the repository
- Check the documentation
- Review existing issues and discussions