---
name: opjsx
description: >-
  Author React UI with the opjsx package's compact, token-efficient template
  syntax. Use when writing React components with opjsx, or when asked to convert
  JSX to/from opjsx. Covers the &tag.class / attr"value" / indented-children
  grammar and ${} interpolation for values, handlers, and .map() lists.
---

# opjsx

`opjsx` is a tagged-template function that renders a compact, indentation-based
markup syntax to React elements. It keeps full React semantics (it compiles to
`React.createElement`) while using ~25–35% fewer tokens than equivalent JSX —
useful whenever an LLM reads or writes the UI.

Use this skill when working in a project that depends on `opjsx`, when asked to
write components in opjsx, or when converting between JSX and opjsx.

## Setup

```bash
pnpm add opjsx react   # or: npm install opjsx react
```

```tsx
import { opjsx } from "opjsx";

export function App() {
  return opjsx`
    &div.app
      &h1
        Hello world
  `;
}
```

`opjsx` is used **as a tagged template** and returns a `ReactElement`. React is a
peer dependency. There is no build/Babel step — parsing happens at runtime.

---

## Grammar

A template is a tree expressed with **indentation**. Each line is either an
**element** (starts with `&`) or a **text node** (anything else). Children are the
lines indented beneath a parent. There are **no closing tags**.

### 1. Elements — `&tag`

Every element line begins with `&`, immediately followed by the tag name:

```
&div
&header
&h1
&my-custom-element
```

- The `&` marker is the **only** thing that distinguishes an element from text, so
  **any** tag name works, including custom elements and web components.
- A line that does **not** start with `&` is a text child (see §5).
- Do not start a *text* line with a literal `&` — it would be parsed as an element.
  If you need leading-`&` text, supply it via interpolation: `${"& co"}`.

### 2. Classes — `.class` (Emmet shorthand)

Append dot-separated classes directly to the tag; they become `className`:

```
&div.card                 ->  <div className="card">
&div.card.active          ->  <div className="card active">
&button.btn.btn-primary   ->  <button className="btn btn-primary">
```

- Dots **separate** classes. Hyphens are **part of** a class name, so
  `&div.nav-container` is one class (`nav-container`), not two.
- If both a `.class` shorthand and a `className"…"` attribute are present, they
  **merge** (dot classes first): `&div.card className"big"` → `className="card big"`.

### 3. Attributes — `name"value"`

Attributes follow the tag/classes, space-separated, written as `name"value"` with
**no `=` sign**:

```
&input type"text" placeholder"Search products..." name"q"
&a href"/about" target"_blank"
&img src"/logo.png" alt"Logo"
```

Values are **type-inferred**:

| Written        | Parsed value      |
| -------------- | ----------------- |
| `tabIndex"3"`  | number `3`        |
| `hidden"true"` | boolean `true`    |
| `x"null"`      | `null`            |
| `type"text"`   | string `"text"`   |

(Quote anything you want to stay a string. Most values are strings.)

### 4. Nesting — indentation

Indentation defines parent/child. No closing tags:

```
&section.hero
  &div.content
    &h1
      Big title
    &p
      Some copy
```

Use consistent indentation (2 spaces is conventional). A child is any line indented
deeper than its parent; siblings share the same indentation.

### 5. Text children

Any indented line that does **not** start with `&` is a text node:

```
&h1
  Welcome to the site
&p
  A paragraph of plain text.
&button.cta
  Get started
```

---

## Interpolation — `${…}`

Because `opjsx` is a tagged template, inject JavaScript with ordinary `${…}`. The
interpolated value's type is preserved through parsing.

### Values

```
&h1
  Hello ${name}
&input value"${query}"
```

- A node that is **exactly** one interpolation keeps its JS type (string, number,
  element, array…).
- An interpolation **embedded in surrounding text** is stringified, e.g.
  `&div.price` / `  $${product.price}` → `"$129.99"`.

### Event handlers

Functions pass straight through. Because they live inside `${…}`, their inner
quotes/braces never confuse the parser:

```
&button.cta onClick"${() => save()}"
  Save
```

⚠️ Moving a handler into `${…}` loses JSX's contextual typing, so **type the event
parameter yourself**:

```
&input onChange"${(e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value)}"
```

### Lists (`.map()`)

Interpolate a `.map()` that returns **nested `opjsx` templates**. Nested results are
passed through as children. Give each item a `key`:

```
&ul.list
  ${items.map((item) => opjsx`
    &li key"${item.id}"
      ${item.name}
  `)}
```

### Dynamic / conditional classes

A computed class can't use the dot shorthand. Use the `className"…"` attribute,
with the interpolation inside the quotes:

```
&div className"card ${isActive ? "active" : ""}"
  ...
```

---

## Full example

```tsx
import { useState, type ChangeEvent } from "react";
import { opjsx } from "opjsx";

type Item = { id: number; name: string; done: boolean };

export function TodoList({ initial }: { initial: Item[] }) {
  const [items, setItems] = useState(initial);
  const [draft, setDraft] = useState("");

  const add = () => {
    if (!draft.trim()) return;
    setItems((xs) => [...xs, { id: Date.now(), name: draft, done: false }]);
    setDraft("");
  };

  const toggle = (id: number) =>
    setItems((xs) => xs.map((x) => (x.id === id ? { ...x, done: !x.done } : x)));

  return opjsx`
    &div.todo
      &h1.title
        My tasks
      &div.row
        &input.field
          type"text"
          placeholder"Add a task..."
          value"${draft}"
          onChange"${(e: ChangeEvent<HTMLInputElement>) => setDraft(e.target.value)}"
        &button.add onClick"${add}"
          Add
      &ul.items
        ${items.map((item) => opjsx`
          &li key"${item.id}" className"item ${item.done ? "done" : ""}"
            &input type"checkbox" checked"${item.done}" onChange"${() => toggle(item.id)}"
            &span
              ${item.name}
        `)}
  `;
}
```

---

## Converting JSX → opjsx

Apply these mechanical rules:

1. `<tag ...>` → `&tag` on its own line; drop the closing `</tag>` (indentation
   replaces it).
2. `className="a b"` → `.a.b` appended to the tag (`&div.a.b`).
3. `prop="value"` → `prop"value"` (remove the `=`).
4. `prop={expr}` → `prop"${expr}"` (wrap the expression in `${…}` inside quotes).
5. JSX children that are elements → indent one level deeper.
6. Text/`{expr}` children → put on their own indented line (`Hello ${name}`).
7. `{list.map((x) => <Tag>…</Tag>)}` → `${list.map((x) => opjsx`&tag …`)}`, keep `key`.

**Before (JSX):**

```jsx
<div className="card">
  <h2>{title}</h2>
  <button onClick={onBuy}>Buy</button>
</div>
```

**After (opjsx):**

```
&div.card
  &h2
    ${title}
  &button onClick"${onBuy}"
    Buy
```

---

## Rules of thumb & common mistakes

- ✅ Start every element line with `&`. Everything else indented is text.
- ✅ Prefer `.class` shorthand; use `className"…"` only for dynamic/conditional classes.
- ✅ One element per line; rely on indentation — never write closing tags.
- ✅ Put all dynamic data, handlers, and lists inside `${…}`.
- ✅ Nested lists use `` opjsx`…` `` inside `.map()`, each item with a `key`.
- ✅ Type event-handler parameters (no JSX contextual typing inside `${…}`).
- ❌ Don't use `=` in attributes (`type="text"` → `type"text"`).
- ❌ Don't wrap children in `{}` — that's a JSX-ism; use a bare text line or `${…}`.
- ❌ Don't forget `&` on an element — without it the line becomes text.
- ❌ Don't start a text line with a literal `&`.

## When to use

Use opjsx when the project depends on `opjsx`, when asked to author or convert
components to opjsx, or when token-efficient UI authoring is a goal. For ordinary
React projects that already use JSX, keep using JSX unless asked to switch.
