Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/reatom/reatom/llms.txt

Use this file to discover all available pages before exploring further.

The @reatom/lit package provides integration between Reatom and Lit, enabling reactive state management in web components with automatic dependency tracking.

Installation

npm install @reatom/lit @reatom/core lit
This package requires Lit 2.6.0 or higher and is currently in alpha.

Core Features

withReatomElement

Create reactive Lit elements that automatically track atom dependencies:
import { LitElement } from 'lit'
import { customElement } from 'lit/decorators.js'
import { html } from '@reatom/lit'
import { withReatomElement } from '@reatom/lit'
import { atom } from '@reatom/core'
import { createContext } from '@reatom/core'

// Create context
const ctx = createContext()
const countAtom = atom(0, 'count')

@customElement('my-counter')
class MyCounter extends withReatomElement(LitElement) {
  render() {
    const count = countAtom()
    
    return html`
      <div>
        <p>Count: ${count}</p>
        <button @click=${() => countAtom.set(count + 1)}>
          Increment
        </button>
      </div>
    `
  }
}
Benefits:
  • Automatic subscription management
  • Re-renders only when accessed atoms change
  • Lifecycle integration (connects/disconnects subscriptions)
  • Works with standard Lit decorators and features

html and svg Templates

Reatom provides enhanced template functions that automatically watch atoms:
import { html, svg } from '@reatom/lit'
import { atom } from '@reatom/core'
import { LitElement } from 'lit'
import { customElement } from 'lit/decorators.js'
import { withReatomElement } from '@reatom/lit'

const messageAtom = atom('Hello', 'message')
const countAtom = atom(0, 'count')

@customElement('greeting-card')
class GreetingCard extends withReatomElement(LitElement) {
  render() {
    // Atoms are automatically watched in templates
    return html`
      <div>
        <h1>${messageAtom}</h1>
        <p>Count: ${countAtom}</p>
      </div>
    `
  }
}
When you use atoms directly in templates, they’re automatically converted to watched values using the watch directive.

watch Directive

Manually watch atoms in templates:
import { LitElement, html as litHtml } from 'lit'
import { customElement } from 'lit/decorators.js'
import { withReatomElement, watch } from '@reatom/lit'
import { atom } from '@reatom/core'

const nameAtom = atom('World', 'name')

@customElement('manual-watch')
class ManualWatch extends withReatomElement(LitElement) {
  render() {
    // Using standard Lit html with manual watch
    return litHtml`
      <div>Hello, ${watch(nameAtom)}!</div>
    `
  }
}
The watch directive:
  • Subscribes to atom changes
  • Updates the template when atom changes
  • Automatically unsubscribes when disconnected
  • Accepts an optional frame parameter

Complete Examples

Counter Component

import { LitElement, css } from 'lit'
import { customElement } from 'lit/decorators.js'
import { html, withReatomElement } from '@reatom/lit'
import { atom, computed } from '@reatom/core'

const countAtom = atom(0, 'count')
const doubledAtom = computed(() => countAtom() * 2, 'doubled')

@customElement('counter-element')
class CounterElement extends withReatomElement(LitElement) {
  static styles = css`
    :host {
      display: block;
      padding: 16px;
      font-family: sans-serif;
    }
    
    button {
      margin: 4px;
      padding: 8px 16px;
      font-size: 14px;
    }
  `
  
  render() {
    return html`
      <div>
        <h2>Count: ${countAtom}</h2>
        <p>Doubled: ${doubledAtom}</p>
        <button @click=${() => countAtom.set(countAtom() + 1)}>+</button>
        <button @click=${() => countAtom.set(countAtom() - 1)}>-</button>
        <button @click=${() => countAtom.set(0)}>Reset</button>
      </div>
    `
  }
}

Todo List Component

import { LitElement, css } from 'lit'
import { customElement, state } from 'lit/decorators.js'
import { html, withReatomElement } from '@reatom/lit'
import { atom, computed } from '@reatom/core'

interface Todo {
  id: number
  text: string
  completed: boolean
}

const todosAtom = atom<Todo[]>([], 'todos')

const activeTodosAtom = computed(
  () => todosAtom().filter(t => !t.completed),
  'activeTodos'
)

const completedTodosAtom = computed(
  () => todosAtom().filter(t => t.completed),
  'completedTodos'
)

@customElement('todo-list')
class TodoList extends withReatomElement(LitElement) {
  static styles = css`
    :host {
      display: block;
      max-width: 600px;
      margin: 0 auto;
      padding: 20px;
    }
    
    .completed {
      text-decoration: line-through;
      opacity: 0.6;
    }
    
    ul {
      list-style: none;
      padding: 0;
    }
    
    li {
      padding: 8px;
      border-bottom: 1px solid #eee;
      display: flex;
      align-items: center;
      gap: 8px;
    }
  `
  
  @state()
  private newTodoText = ''
  
  private addTodo() {
    if (this.newTodoText.trim()) {
      todosAtom.set([
        ...todosAtom(),
        {
          id: Date.now(),
          text: this.newTodoText,
          completed: false
        }
      ])
      this.newTodoText = ''
    }
  }
  
  private toggleTodo(id: number) {
    todosAtom.set(
      todosAtom().map(todo =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    )
  }
  
  private removeTodo(id: number) {
    todosAtom.set(todosAtom().filter(todo => todo.id !== id))
  }
  
  render() {
    const todos = todosAtom()
    const active = activeTodosAtom()
    const completed = completedTodosAtom()
    
    return html`
      <div>
        <h1>Todo List</h1>
        <p>Active: ${active.length} | Completed: ${completed.length}</p>
        
        <div>
          <input
            .value=${this.newTodoText}
            @input=${(e: InputEvent) => {
              this.newTodoText = (e.target as HTMLInputElement).value
            }}
            @keypress=${(e: KeyboardEvent) => {
              if (e.key === 'Enter') this.addTodo()
            }}
            placeholder="Add a todo..."
          />
          <button @click=${() => this.addTodo()}>Add</button>
        </div>
        
        <ul>
          ${todos.map(todo => html`
            <li>
              <input
                type="checkbox"
                .checked=${todo.completed}
                @change=${() => this.toggleTodo(todo.id)}
              />
              <span class=${todo.completed ? 'completed' : ''}>
                ${todo.text}
              </span>
              <button @click=${() => this.removeTodo(todo.id)}>Delete</button>
            </li>
          `)}
        </ul>
      </div>
    `
  }
}

Multi-Component App

import { LitElement, css } from 'lit'
import { customElement, property } from 'lit/decorators.js'
import { html, withReatomElement } from '@reatom/lit'
import { atom, computed } from '@reatom/core'

// Shared state
const userAtom = atom<{ name: string; email: string } | null>(null, 'user')
const themeAtom = atom<'light' | 'dark'>('light', 'theme')

// Header component
@customElement('app-header')
class AppHeader extends withReatomElement(LitElement) {
  static styles = css`
    header {
      padding: 16px;
      background: var(--header-bg, #333);
      color: var(--header-color, white);
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
  `
  
  render() {
    const user = userAtom()
    const theme = themeAtom()
    
    return html`
      <header>
        <h1>My App</h1>
        <div>
          <span>${user ? user.name : 'Guest'}</span>
          <button @click=${() => {
            themeAtom.set(theme === 'light' ? 'dark' : 'light')
          }}>
            Toggle ${theme === 'light' ? '🌙' : '☀️'}
          </button>
        </div>
      </header>
    `
  }
}

// Profile component
@customElement('user-profile')
class UserProfile extends withReatomElement(LitElement) {
  static styles = css`
    :host {
      display: block;
      padding: 16px;
    }
  `
  
  private login() {
    userAtom.set({
      name: 'John Doe',
      email: 'john@example.com'
    })
  }
  
  private logout() {
    userAtom.set(null)
  }
  
  render() {
    const user = userAtom()
    
    if (!user) {
      return html`
        <button @click=${() => this.login()}>Login</button>
      `
    }
    
    return html`
      <div>
        <h2>${user.name}</h2>
        <p>${user.email}</p>
        <button @click=${() => this.logout()}>Logout</button>
      </div>
    `
  }
}

// Main app
@customElement('my-app')
class MyApp extends withReatomElement(LitElement) {
  render() {
    const theme = themeAtom()
    
    return html`
      <div class="app ${theme}">
        <app-header></app-header>
        <user-profile></user-profile>
      </div>
    `
  }
}

SVG with Atoms

import { LitElement } from 'lit'
import { customElement } from 'lit/decorators.js'
import { svg, withReatomElement } from '@reatom/lit'
import { atom, computed } from '@reatom/core'

const radiusAtom = atom(50, 'radius')
const colorAtom = atom('blue', 'color')

const circumferenceAtom = computed(
  () => 2 * Math.PI * radiusAtom(),
  'circumference'
)

@customElement('circle-demo')
class CircleDemo extends withReatomElement(LitElement) {
  render() {
    return svg`
      <svg width="200" height="200">
        <circle
          cx="100"
          cy="100"
          r="${radiusAtom}"
          fill="${colorAtom}"
        />
        <text x="100" y="190" text-anchor="middle">
          Circumference: ${computed(() => 
            circumferenceAtom().toFixed(2)
          )}
        </text>
      </svg>
      <div>
        <input
          type="range"
          min="10"
          max="90"
          .value=${String(radiusAtom())}
          @input=${(e: Event) => {
            radiusAtom.set(Number((e.target as HTMLInputElement).value))
          }}
        />
        <input
          type="color"
          .value=${colorAtom()}
          @input=${(e: Event) => {
            colorAtom.set((e.target as HTMLInputElement).value)
          }}
        />
      </div>
    `
  }
}

TypeScript Support

Full TypeScript support with decorators:
import { LitElement } from 'lit'
import { customElement, property } from 'lit/decorators.js'
import { html, withReatomElement } from '@reatom/lit'
import { atom } from '@reatom/core'

interface Product {
  id: number
  name: string
  price: number
}

const cartAtom = atom<Product[]>([], 'cart')

@customElement('product-card')
class ProductCard extends withReatomElement(LitElement) {
  @property({ type: Object })
  product!: Product
  
  render() {
    const cart = cartAtom()
    const inCart = cart.some(p => p.id === this.product.id)
    
    return html`
      <div>
        <h3>${this.product.name}</h3>
        <p>$${this.product.price}</p>
        <button
          @click=${() => {
            if (!inCart) {
              cartAtom.set([...cart, this.product])
            }
          }}
          ?disabled=${inCart}
        >
          ${inCart ? 'In Cart' : 'Add to Cart'}
        </button>
      </div>
    `
  }
}

Best Practices

1

Always extend with withReatomElement

Use withReatomElement(LitElement) as your base class to enable automatic atom tracking.
2

Use Reatom html and svg

Import template functions from @reatom/lit for automatic atom watching in templates.
3

Share atoms across components

Define atoms outside components to share state across multiple web components.
4

Leverage computed atoms

Use computed atoms for derived state to maintain reactivity across the component tree.

Lifecycle Integration

The withReatomElement mixin integrates with Lit’s lifecycle:
  • connectedCallback: Starts tracking atoms and subscribes to changes
  • disconnectedCallback: Unsubscribes from all atoms
  • shouldUpdate: Optimized to prevent unnecessary updates
  • render: Automatically re-renders when tracked atoms change

Next Steps

  • Learn about Core Concepts for atoms and state management
  • Explore Web Components for building custom elements
  • Check out [Asynchttps://github.com/reatom/reatom/tree/main/packages for handling asynchronous state