Skip to content

Lit Version โ€‹

HaloLight Lit version is built on Lit 3 with Web Components standards + TypeScript, providing cross-framework reusable Web Components library.

Live Preview: https://halolight-lit.h7ml.cn

GitHub: https://github.com/halolight/halolight-lit

Features โ€‹

  • ๐ŸŽฏ Web Components Standard - Native browser support, no framework lock-in
  • โšก Cross-framework Reusable - Components work in React/Vue/Angular
  • ๐ŸŽจ Theme System - 11 skins, light/dark mode, View Transitions
  • ๐Ÿ” Authentication - Complete login/register/password recovery flow
  • ๐Ÿ“Š Dashboard - Data visualization and business management
  • ๐Ÿ›ก๏ธ Permission Control - RBAC fine-grained permission management
  • ๐Ÿชถ Lightweight - Core library ~5KB gzip
  • ๐ŸŒ“ Shadow DOM - Style isolation, avoid conflicts

Tech Stack โ€‹

TechnologyVersionDescription
Lit3.xWeb Components framework
TypeScript5.xType safety
Tailwind CSS4.xAtomic CSS
@lit-labs/router0.1.xClient-side routing
@lit-labs/context1.xContext state
Shoelace2.xWeb Components UI library
Zod3.xData validation
ECharts5.xChart visualization
Vite6.xBuild tool
Mock.js1.xData mocking

Core Features โ€‹

  • Configurable Dashboard - 9 widgets, drag & drop layout, responsive design
  • Permission System - RBAC permission control, route guards, permission components
  • Theme System - 11 skins, light/dark mode, View Transitions
  • Reactive Properties - @property decorator for reactivity
  • Shadow DOM Isolation - Style encapsulation, avoid global conflicts
  • Native Support - Based on Web standards, compatible with all modern browsers

Directory Structure โ€‹

halolight-lit/
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ pages/                     # Page components
โ”‚   โ”‚   โ”œโ”€โ”€ hl-home.ts            # Homepage
โ”‚   โ”‚   โ”œโ”€โ”€ auth/                 # Auth pages
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ hl-login.ts
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ hl-register.ts
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ hl-forgot-password.ts
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ hl-reset-password.ts
โ”‚   โ”‚   โ””โ”€โ”€ dashboard/            # Dashboard pages
โ”‚   โ”‚       โ”œโ”€โ”€ hl-dashboard.ts
โ”‚   โ”‚       โ”œโ”€โ”€ hl-users.ts
โ”‚   โ”‚       โ”œโ”€โ”€ hl-user-detail.ts
โ”‚   โ”‚       โ”œโ”€โ”€ hl-user-create.ts
โ”‚   โ”‚       โ”œโ”€โ”€ hl-roles.ts
โ”‚   โ”‚       โ”œโ”€โ”€ hl-permissions.ts
โ”‚   โ”‚       โ”œโ”€โ”€ hl-settings.ts
โ”‚   โ”‚       โ””โ”€โ”€ hl-profile.ts
โ”‚   โ”œโ”€โ”€ components/               # Component library
โ”‚   โ”‚   โ”œโ”€โ”€ ui/                   # UI components
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ hl-button.ts
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ hl-input.ts
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ hl-card.ts
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ hl-dialog.ts
โ”‚   โ”‚   โ”œโ”€โ”€ layout/               # Layout components
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ hl-admin-layout.ts
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ hl-auth-layout.ts
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ hl-sidebar.ts
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ hl-header.ts
โ”‚   โ”‚   โ”œโ”€โ”€ dashboard/            # Dashboard components
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ hl-dashboard-grid.ts
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ hl-widget-wrapper.ts
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ hl-stats-widget.ts
โ”‚   โ”‚   โ””โ”€โ”€ shared/               # Shared components
โ”‚   โ”‚       โ””โ”€โ”€ hl-permission-guard.ts
โ”‚   โ”œโ”€โ”€ stores/                   # State management
โ”‚   โ”‚   โ”œโ”€โ”€ auth-context.ts
โ”‚   โ”‚   โ”œโ”€โ”€ ui-settings-context.ts
โ”‚   โ”‚   โ””โ”€โ”€ dashboard-context.ts
โ”‚   โ”œโ”€โ”€ lib/                      # Utilities
โ”‚   โ”‚   โ”œโ”€โ”€ api.ts
โ”‚   โ”‚   โ”œโ”€โ”€ permission.ts
โ”‚   โ”‚   โ””โ”€โ”€ styles.ts
โ”‚   โ”œโ”€โ”€ mock/                     # Mock data
โ”‚   โ”œโ”€โ”€ types/                    # Type definitions
โ”‚   โ”œโ”€โ”€ hl-app.ts                 # Root component
โ”‚   โ”œโ”€โ”€ router.ts                 # Route config
โ”‚   โ””โ”€โ”€ main.ts                   # Entry file
โ”œโ”€โ”€ public/                       # Static assets
โ”œโ”€โ”€ vite.config.ts               # Vite config
โ”œโ”€โ”€ tailwind.config.ts           # Tailwind config
โ””โ”€โ”€ package.json

Quick Start โ€‹

Requirements โ€‹

  • Node.js >= 18.0.0
  • pnpm >= 9.x

Installation โ€‹

bash
git clone https://github.com/halolight/halolight-lit.git
cd halolight-lit
pnpm install

Environment Variables โ€‹

bash
cp .env.example .env
env
# .env
VITE_API_URL=/api
VITE_USE_MOCK=true
VITE_DEMO_EMAIL=admin@halolight.h7ml.cn
VITE_DEMO_PASSWORD=123456
VITE_SHOW_DEMO_HINT=false
VITE_APP_TITLE=Admin Pro
VITE_BRAND_NAME=Halolight

Start Development โ€‹

bash
pnpm dev

Visit http://localhost:5173

Production Build โ€‹

bash
pnpm build
pnpm preview

Core Features โ€‹

State Management (@lit-labs/context) โ€‹

ts
// stores/auth-context.ts
import { createContext } from '@lit-labs/context'
import { html, LitElement } from 'lit'
import { customElement, state } from 'lit/decorators.js'
import { provide } from '@lit-labs/context'

interface User {
  id: number
  name: string
  email: string
  permissions: string[]
}

interface AuthState {
  user: User | null
  token: string | null
  loading: boolean
  login: (credentials: { email: string; password: string }) => Promise<void>
  logout: () => void
  hasPermission: (permission: string) => boolean
}

export const authContext = createContext<AuthState>('auth')

@customElement('hl-auth-provider')
export class AuthProvider extends LitElement {
  @state() private user: User | null = null
  @state() private token: string | null = null
  @state() private loading = false

  @provide({ context: authContext })
  authState: AuthState = {
    user: null,
    token: null,
    loading: false,
    login: this.login.bind(this),
    logout: this.logout.bind(this),
    hasPermission: this.hasPermission.bind(this),
  }

  connectedCallback() {
    super.connectedCallback()
    this.loadFromStorage()
  }

  private loadFromStorage() {
    const saved = localStorage.getItem('auth')
    if (saved) {
      const { user, token } = JSON.parse(saved)
      this.user = user
      this.token = token
      this.updateContext()
    }
  }

  private updateContext() {
    this.authState = {
      ...this.authState,
      user: this.user,
      token: this.token,
      loading: this.loading,
    }
  }

  async login(credentials: { email: string; password: string }) {
    this.loading = true
    this.updateContext()

    try {
      const response = await fetch('/api/auth/login', {
        method: 'POST',
        body: JSON.stringify(credentials),
        headers: { 'Content-Type': 'application/json' },
      })
      const data = await response.json()

      this.user = data.user
      this.token = data.token
      localStorage.setItem('auth', JSON.stringify({
        user: this.user,
        token: this.token,
      }))
    } finally {
      this.loading = false
      this.updateContext()
    }
  }

  logout() {
    this.user = null
    this.token = null
    localStorage.removeItem('auth')
    this.updateContext()
  }

  hasPermission(permission: string): boolean {
    const perms = this.user?.permissions ?? []
    return perms.some(p =>
      p === '*' || p === permission ||
      (p.endsWith(':*') && permission.startsWith(p.slice(0, -1)))
    )
  }

  render() {
    return html`<slot></slot>`
  }
}

Base Component โ€‹

ts
// components/ui/hl-button.ts
import { LitElement, html, css } from 'lit'
import { customElement, property } from 'lit/decorators.js'
import { classMap } from 'lit/directives/class-map.js'

@customElement('hl-button')
export class HlButton extends LitElement {
  static styles = css`
    :host {
      display: inline-block;
    }

    button {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      border-radius: 0.375rem;
      font-weight: 500;
      cursor: pointer;
      transition: all 0.2s;
    }

    .default {
      background-color: var(--primary);
      color: var(--primary-foreground);
    }

    .default:hover {
      opacity: 0.9;
    }

    .destructive {
      background-color: var(--destructive);
      color: var(--destructive-foreground);
    }

    .outline {
      border: 1px solid var(--border);
      background: transparent;
    }

    .sm { height: 2rem; padding: 0 0.75rem; font-size: 0.875rem; }
    .md { height: 2.5rem; padding: 0 1rem; }
    .lg { height: 3rem; padding: 0 1.5rem; font-size: 1.125rem; }

    .disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }
  `

  @property() variant: 'default' | 'destructive' | 'outline' | 'ghost' = 'default'
  @property() size: 'sm' | 'md' | 'lg' = 'md'
  @property({ type: Boolean }) disabled = false

  render() {
    const classes = {
      [this.variant]: true,
      [this.size]: true,
      disabled: this.disabled,
    }

    return html`
      <button class=${classMap(classes)} ?disabled=${this.disabled}>
        <slot></slot>
      </button>
    `
  }
}

Route Configuration โ€‹

ts
// router.ts
import { Router } from '@lit-labs/router'
import { html } from 'lit'

// Lazy load page components
const routes = [
  {
    path: '/',
    render: () => html`<hl-home></hl-home>`,
    enter: async () => {
      await import('./pages/hl-home.js')
      return true
    },
  },
  {
    path: '/login',
    render: () => html`<hl-login></hl-login>`,
    enter: async () => {
      await import('./pages/auth/hl-login.js')
      return true
    },
  },
  {
    path: '/dashboard',
    render: () => html`<hl-dashboard></hl-dashboard>`,
    enter: async ({ router }) => {
      // Route guard
      const authState = document.querySelector('hl-auth-provider')?.authState
      if (!authState?.token) {
        router.goto('/login?redirect=/dashboard')
        return false
      }
      await import('./pages/dashboard/hl-dashboard.js')
      return true
    },
  },
  {
    path: '/users',
    render: () => html`<hl-users></hl-users>`,
    enter: async ({ router }) => {
      const authState = document.querySelector('hl-auth-provider')?.authState
      if (!authState?.hasPermission('users:list')) {
        return false
      }
      await import('./pages/dashboard/hl-users.js')
      return true
    },
  },
  // More routes...
]

export function createRouter(host: HTMLElement) {
  return new Router(host, routes)
}

Permission Control โ€‹

ts
// components/shared/hl-permission-guard.ts
import { LitElement, html } from 'lit'
import { customElement, property } from 'lit/decorators.js'
import { consume } from '@lit-labs/context'
import { authContext, type AuthState } from '../../stores/auth-context'

@customElement('hl-permission-guard')
export class HlPermissionGuard extends LitElement {
  @property() permission = ''

  @consume({ context: authContext, subscribe: true })
  authState!: AuthState

  render() {
    const hasPermission = this.authState?.hasPermission(this.permission)

    if (!hasPermission) {
      return html`<slot name="fallback"></slot>`
    }

    return html`<slot></slot>`
  }
}

Usage Example:

html
<hl-permission-guard permission="users:delete">
  <hl-button variant="destructive">Delete</hl-button>
  <span slot="fallback" class="text-muted-foreground">No permission</span>
</hl-permission-guard>

Draggable Dashboard โ€‹

ts
// components/dashboard/hl-dashboard-grid.ts
import { LitElement, html, css } from 'lit'
import { customElement, state } from 'lit/decorators.js'
import Sortable from 'sortablejs'

@customElement('hl-dashboard-grid')
export class HlDashboardGrid extends LitElement {
  static styles = css`
    .grid {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
      gap: 1rem;
    }

    .widget {
      background: var(--card);
      border-radius: 0.5rem;
      padding: 1rem;
      cursor: move;
    }
  `

  @state() private widgets = [
    { id: 'stats', type: 'stats' },
    { id: 'chart', type: 'chart' },
    { id: 'table', type: 'table' },
  ]

  firstUpdated() {
    const grid = this.shadowRoot?.querySelector('.grid')
    if (grid) {
      new Sortable(grid as HTMLElement, {
        animation: 150,
        onEnd: (evt) => {
          const { oldIndex, newIndex } = evt
          if (oldIndex !== undefined && newIndex !== undefined) {
            const item = this.widgets.splice(oldIndex, 1)[0]
            this.widgets.splice(newIndex, 0, item)
            this.requestUpdate()
          }
        },
      })
    }
  }

  render() {
    return html`
      <div class="grid">
        ${this.widgets.map(widget => html`
          <div class="widget" data-id=${widget.id}>
            <hl-widget-wrapper type=${widget.type}></hl-widget-wrapper>
          </div>
        `)}
      </div>
    `
  }
}

Theme System โ€‹

Skin Presets โ€‹

Supports 11 preset skins, switch via quick settings panel:

SkinPrimary ColorCSS Variable
DefaultPurple--primary: 51.1% 0.262 276.97
BlueBlue--primary: 54.8% 0.243 264.05
EmeraldEmerald--primary: 64.6% 0.178 142.49
RoseRose--primary: 58.5% 0.217 12.53
OrangeOrange--primary: 68.4% 0.197 41.73

CSS Variables (OKLch) โ€‹

css
/* Theme variable definition */
:root {
  --background: 100% 0 0;
  --foreground: 14.9% 0.017 285.75;
  --primary: 51.1% 0.262 276.97;
  --primary-foreground: 98% 0 0;
  --card: 100% 0 0;
  --card-foreground: 14.9% 0.017 285.75;
  --border: 93.3% 0.011 285.88;
  --radius: 0.5rem;
}

.dark {
  --background: 14.9% 0.017 285.75;
  --foreground: 98% 0 0;
  --primary: 51.1% 0.262 276.97;
  --primary-foreground: 98% 0 0;
  --card: 14.9% 0.017 285.75;
  --card-foreground: 98% 0 0;
  --border: 25.1% 0.025 285.82;
}

Page Routes โ€‹

PathPagePermission
/HomepagePublic
/loginLoginPublic
/registerRegisterPublic
/forgot-passwordForgot PasswordPublic
/reset-passwordReset PasswordPublic
/dashboardDashboarddashboard:view
/usersUser Managementusers:view
/users/createCreate Userusers:create
/users/:idUser Detailusers:view
/rolesRole Managementroles:view
/permissionsPermission Managementpermissions:view
/settingsSystem Settingssettings:view
/profileProfilesettings:view

Using in Other Frameworks โ€‹

React โ€‹

tsx
import '@halolight/lit/hl-button'

function App() {
  return (
    <hl-button variant="default" onClick={() => console.log('clicked')}>
      Click
    </hl-button>
  )
}

Vue โ€‹

vue
<template>
  <hl-button variant="default" @click="handleClick">
    Click
  </hl-button>
</template>

<script setup>
import '@halolight/lit/hl-button'

function handleClick() {
  console.log('clicked')
}
</script>

Angular โ€‹

ts
// app.module.ts
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'
import '@halolight/lit/hl-button'

@NgModule({
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
html
<hl-button variant="default" (click)="handleClick()">
  Click
</hl-button>

Common Commands โ€‹

bash
pnpm dev            # Start dev server
pnpm build          # Production build
pnpm preview        # Preview production build
pnpm lint           # Code linting
pnpm lint:fix       # Auto fix
pnpm type-check     # Type checking
pnpm test           # Run tests
pnpm test:coverage  # Test coverage

Deployment โ€‹

Deploy with Vercel

Docker โ€‹

dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Other Platforms โ€‹

Demo Accounts โ€‹

RoleEmailPassword
Adminadmin@halolight.h7ml.cn123456
Useruser@halolight.h7ml.cn123456

Testing โ€‹

bash
pnpm test           # Run tests (watch mode)
pnpm test:run       # Single run
pnpm test:coverage  # Coverage report
pnpm test:ui        # Vitest UI

Test Example โ€‹

ts
// __tests__/hl-button.test.ts
import { expect, fixture, html } from '@open-wc/testing'
import '../src/components/ui/hl-button'

describe('hl-button', () => {
  it('renders with default variant', async () => {
    const el = await fixture(html`<hl-button>Click me</hl-button>`)
    const button = el.shadowRoot?.querySelector('button')
    expect(button).to.exist
    expect(button?.textContent?.trim()).to.equal('Click me')
  })

  it('applies variant classes', async () => {
    const el = await fixture(html`<hl-button variant="destructive">Delete</hl-button>`)
    const button = el.shadowRoot?.querySelector('button')
    expect(button?.classList.contains('destructive')).to.be.true
  })

  it('handles disabled state', async () => {
    const el = await fixture(html`<hl-button disabled>Disabled</hl-button>`)
    const button = el.shadowRoot?.querySelector('button')
    expect(button?.disabled).to.be.true
  })
})

Configuration โ€‹

Vite Configuration โ€‹

ts
// vite.config.ts
import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    lib: {
      entry: 'src/main.ts',
      formats: ['es'],
    },
    rollupOptions: {
      external: /^lit/,
    },
  },
  server: {
    port: 5173,
  },
})

Tailwind Configuration โ€‹

ts
// tailwind.config.ts
import type { Config } from 'tailwindcss'

export default {
  content: ['./index.html', './src/**/*.{ts,js}'],
  darkMode: 'class',
  theme: {
    extend: {
      colors: {
        border: 'oklch(var(--border))',
        background: 'oklch(var(--background))',
        foreground: 'oklch(var(--foreground))',
        primary: {
          DEFAULT: 'oklch(var(--primary))',
          foreground: 'oklch(var(--primary-foreground))',
        },
      },
    },
  },
} satisfies Config

CI/CD โ€‹

Complete GitHub Actions CI workflow configured:

yaml
# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm lint
      - run: pnpm type-check

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm test:coverage
      - uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm build

  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
      - run: pnpm audit --audit-level=high

Advanced Features โ€‹

Lifecycle Hooks โ€‹

ts
// Component lifecycle
@customElement('my-component')
export class MyComponent extends LitElement {
  // First connected to DOM
  connectedCallback() {
    super.connectedCallback()
    console.log('Component connected')
  }

  // First update complete
  firstUpdated(changedProperties: PropertyValues) {
    super.firstUpdated(changedProperties)
    console.log('First render complete')
  }

  // Each update complete
  updated(changedProperties: PropertyValues) {
    super.updated(changedProperties)
    if (changedProperties.has('value')) {
      console.log('Value changed:', this.value)
    }
  }

  // Removed from DOM
  disconnectedCallback() {
    super.disconnectedCallback()
    console.log('Component disconnected')
  }
}

Custom Directives โ€‹

ts
// lib/directives/tooltip.ts
import { directive, Directive } from 'lit/directive.js'
import { AsyncDirective } from 'lit/async-directive.js'

class TooltipDirective extends AsyncDirective {
  render(text: string) {
    return text
  }

  update(part: any, [text]: [string]) {
    const element = part.element
    element.setAttribute('title', text)
    element.style.cursor = 'help'
    return this.render(text)
  }
}

export const tooltip = directive(TooltipDirective)
ts
// Usage
import { tooltip } from './lib/directives/tooltip'

render() {
  return html`
    <span ${tooltip('Tooltip message')}>Hover to see tooltip</span>
  `
}

Performance Optimization โ€‹

Virtual Scrolling โ€‹

ts
// components/ui/hl-virtual-list.ts
import { LitElement, html } from 'lit'
import { customElement, property, state } from 'lit/decorators.js'
import { repeat } from 'lit/directives/repeat.js'

@customElement('hl-virtual-list')
export class HlVirtualList extends LitElement {
  @property({ type: Array }) items: any[] = []
  @property({ type: Number }) itemHeight = 50
  @state() private visibleStart = 0
  @state() private visibleEnd = 20

  private handleScroll(e: Event) {
    const target = e.target as HTMLElement
    const scrollTop = target.scrollTop
    this.visibleStart = Math.floor(scrollTop / this.itemHeight)
    this.visibleEnd = this.visibleStart + 20
  }

  render() {
    const visibleItems = this.items.slice(this.visibleStart, this.visibleEnd)

    return html`
      <div class="container" @scroll=${this.handleScroll}>
        <div style="height: ${this.items.length * this.itemHeight}px">
          <div style="transform: translateY(${this.visibleStart * this.itemHeight}px)">
            ${repeat(
              visibleItems,
              item => item.id,
              item => html`<div class="item">${item.name}</div>`
            )}
          </div>
        </div>
      </div>
    `
  }
}

Lazy Loading Components โ€‹

ts
// Route lazy loading
{
  path: '/dashboard',
  enter: async () => {
    await import('./pages/dashboard/hl-dashboard.js')
    return true
  },
}

// Dynamic import
async loadWidget(type: string) {
  const module = await import(`./widgets/hl-${type}-widget.js`)
  return module.default
}

Preloading โ€‹

ts
// Preload critical routes
const preloadRoutes = ['/dashboard', '/users']

preloadRoutes.forEach(async (route) => {
  const link = document.createElement('link')
  link.rel = 'modulepreload'
  link.href = `./pages${route}.js`
  document.head.appendChild(link)
})

FAQ โ€‹

Q: How to use global styles in Shadow DOM? โ€‹

A: Use CSS custom properties or @import global styles:

ts
static styles = css`
  @import url('/global.css');

  :host {
    color: var(--foreground);
    background: var(--background);
  }
`

Q: How to handle form data two-way binding? โ€‹

A: Use @input event and @state decorator:

ts
@customElement('hl-form')
export class HlForm extends LitElement {
  @state() private formData = { name: '', email: '' }

  private handleInput(field: string, value: string) {
    this.formData = { ...this.formData, [field]: value }
  }

  render() {
    return html`
      <input
        .value=${this.formData.name}
        @input=${(e: Event) =>
          this.handleInput('name', (e.target as HTMLInputElement).value)}
      />
    `
  }
}

Q: How to communicate between components? โ€‹

A: Use custom events or Context API:

ts
// Dispatch event
this.dispatchEvent(new CustomEvent('data-changed', {
  detail: { data: this.data },
  bubbles: true,
  composed: true, // Penetrate Shadow DOM
}))

// Listen event
@customElement('parent-component')
export class ParentComponent extends LitElement {
  render() {
    return html`
      <child-component @data-changed=${this.handleDataChanged}></child-component>
    `
  }

  private handleDataChanged(e: CustomEvent) {
    console.log('Data:', e.detail.data)
  }
}

Comparison with Other Versions โ€‹

FeatureLit VersionNext.js VersionVue Version
SSR/SSGโœ… (Experimental)โœ…โœ… (Nuxt)
State Management@lit-labs/contextZustandPinia
Routing@lit-labs/routerApp RouterVue Router
Build ToolViteNext.jsVite
Cross-framework Reusableโœ… Native SupportโŒโŒ
Shadow DOMโœ…โŒโŒ
Bundle Size5KB (gzip)~90KB~60KB