Practical examples. Live demos.

Explore small Nuclo patterns with interactive previews and source code beside each behavior.

Counter

Mutate state, then call update(). The dynamic text refreshes.

0
COUNT
Counter.ts
import 'nuclo'

let count = 0

export function Counter() {
  return div(
    p(() => count),
    p("COUNT"),
    button("-", { onClick: () => { count--; update() } }),
    button("Reset", { onClick: () => { count = 0; update() } }),
    button("+", { onClick: () => { count++; update() } }),
  )
}

Todo List

Use list() for repeated items. Call update() after each mutation.

No tasks yet.
0 of 0 remaining
TodoList.ts
import 'nuclo'

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

let todos: Todo[] = []
let filter: "all" | "active" | "done" = "all"
let text = ""
let nextId = 1

function visible() {
  if (filter === "active") return todos.filter((t) => !t.done)
  if (filter === "done") return todos.filter((t) => t.done)
  return todos
}

function addTodo() {
  if (!text.trim()) return

  todos.push({ id: nextId++, text, done: false })
  text = ""
  update()
}

export function TodoList() {
  return div(
    input(
      { placeholder: "Add a task..." },
      {
        onInput: (event) => {
          text = (event.target as HTMLInputElement).value
        },
      },
    ),
    button("Add", { onClick: addTodo }),
    div(
      button("All", { onClick: () => { filter = "all"; update() } }),
      button("Active", { onClick: () => { filter = "active"; update() } }),
      button("Done", { onClick: () => { filter = "done"; update() } }),
    ),
    ul(
      list(
        () => visible(),
        (todo) => li(
          input(
            { type: "checkbox", checked: () => todo.done },
            { onChange: () => { todo.done = !todo.done; update() } },
          ),
          span(() => todo.done ? todo.text + " done" : todo.text),
          button("x", { onClick: () => {
            todos = todos.filter((item) => item.id !== todo.id)
            update()
          } }),
        ),
      ),
      when(() => visible().length === 0, div("No tasks yet.")),
    ),
    p(() => `${todos.filter((t) => !t.done).length} of ${todos.length} remaining`),
  )
}

Search Filter

Filter an array with plain JavaScript. Render the result with list().

SearchFilter.ts
import 'nuclo'

const users = [
  { name: "Alice Chen", email: "alice@example.com", initials: "AC" },
  { name: "Bob Smith", email: "bob@example.com", initials: "BS" },
  { name: "Charlie Davis", email: "charlie@example.com", initials: "CD" },
  { name: "Diana Park", email: "diana@example.com", initials: "DP" },
]
let query = ""

function results() {
  const q = query.toLowerCase()
  if (!q) return users
  return users.filter((user) =>
    user.name.toLowerCase().includes(q) ||
    user.email.toLowerCase().includes(q)
  )
}

export function SearchFilter() {
  return div(
    input(
      { placeholder: "Search users..." },
      {
        onInput: (event) => {
          query = (event.target as HTMLInputElement).value
          update()
        },
      },
    ),
    div(
      list(
        () => results(),
        (user) => div(
          div(user.initials),
          div(user.name),
          div(user.email),
        ),
      ),
      when(() => results().length === 0, div("No users found.")),
    ),
  )
}

Styling

Use css() for classes. Use cx() for small conditional changes.

css() creates the base class. cx() adds the selected class only when state changes.

Styling.ts
import 'nuclo'

const { css, cx } = createCss({
  colors: {
    primary: "#ff3f00",
    border: "#e5e7eb",
    text: "#1f2937",
  },
})

const baseButton = css("content-examples-baseButton", {
  px: 14,
  py: 9,
  rounded: 6,
  border: "1px solid",
  borderColor: "border",
  color: "text",
  cursor: "pointer",
})

const selectedButton = css("content-examples-selectedButton", {
  bg: "primary",
  color: "white",
  borderColor: "primary",
})

let selected = false

export function StyledButton() {
  return button(
    () => cx(baseButton, selected && selectedButton),
    () => selected ? "Selected" : "Select",
    { onClick: () => { selected = !selected; update() } },
  )
}

Styling basics

A short set of styling examples: create a class, compose a class, and use a tiny theme.

css()

Create a class from one typed style object.

css.ts
const card = css("pages-examples-styling-gallery-card", {
  p: 16,
  rounded: 8,
  border: "1px solid",
  borderColor: "border",
  bg: "surface",
  color: "text",
})

function Card() {
  return div(card, "Simple card")
}

cx()

Compose classes conditionally. Later styles win.

cx.ts
const baseButton = css("pages-examples-styling-gallery-baseButton", {
  px: 14,
  py: 9,
  rounded: 6,
  border: "1px solid",
  borderColor: "border",
})

const selectedButton = css("pages-examples-styling-gallery-selectedButton", {
  bg: "primary",
  color: "white",
  borderColor: "primary",
})

let selected = false

function Button() {
  return button(
    () => cx(baseButton, selected && selectedButton),
    () => selected ? "Selected" : "Select",
    { onClick: () => { selected = !selected; update() } },
  )
}

createCss()

Define a small theme and use its tokens.

createCss.ts
const { css } = createCss({
  colors: {
    primary: "#ff3f00",
    surface: "#fff7ed",
    border: "#ffd7c2",
  },
  screens: {
    sm: "(min-width: 520px)",
  },
})

const box = css("pages-examples-styling-gallery-box", {
  p: 12,
  bg: "surface",
  borderColor: "border",
  sm: { p: 20, borderColor: "primary" },
})