# Customize The UI

LivePanels is headless, but it is not empty.

It gives you runtime components, DOM attrs, browser hooks, structural CSS, and
projection data. Your application supplies the visible UI: page layout,
toolbar, item frames, node cards, drawing palette, inspectors, menus, and theme.

The practical rule is: use LivePanels helpers for behavior; use your own markup
and CSS for presentation.

## Choose A Toolkit

Toolkits are `use` presets for components and attrs:

| Toolkit | Adds |
|---|---|
| `LivePanels.Toolkit.Canvas` | Runtime root, item frames, drag, resize, item controls, selection, layout, history, clipboard, viewport JS. |
| `LivePanels.Toolkit.Graph` | Canvas plus graph ports, connector attrs, graph selection, fit helpers. |
| `LivePanels.Toolkit.Drawing` | Canvas plus drawing mode, style, vector item, erase, and point helpers. |

Use the preset after mounting the canvas runtime:

```elixir
use LivePanels.Canvas,
  pubsub: MyApp.PubSub

use LivePanels.Toolkit.Canvas
```

Use focused imports when you want direct control:

```elixir
use LivePanels.Canvas,
  pubsub: MyApp.PubSub

import LivePanels.Toolkit.Runtime
import LivePanels.Toolkit.Controls.Item
```

Focused imports keep the runtime unchanged while limiting which helpers enter
the application module.

## Render The Runtime Root

Most applications render one runtime component:

```heex
<.canvas_runtime {canvas_runtime_attrs(assigns, class: "canvas-root")}>
  <:item :let={context}>
    ...
  </:item>
</.canvas_runtime>
```

`canvas_runtime_attrs/2` passes the canvas id, client id, viewport state,
runtime hooks, mode, and protocol attrs the browser needs. Put application classes and
data attrs in the overrides.

Use slots for layer-specific customization when needed:

```heex
<.canvas_runtime {canvas_runtime_attrs(assigns)}>
  <:world_layer>
    <svg class="custom-grid" aria-hidden="true"></svg>
  </:world_layer>

  <:item :let={context}>
    <.item_shell context={context} />
  </:item>

  <:world_overlay>
    <g class="presence-cursors" aria-hidden="true"></g>
  </:world_overlay>

  <:viewport_layer>
    <div class="canvas-hud">...</div>
  </:viewport_layer>
</.canvas_runtime>
```

`:world_layer` is camera-addressed and sits below LivePanels items.
`:world_overlay` is camera-addressed and sits above LivePanels items. Use it for
world-space overlays such as pointer presence. `:viewport_layer` is screen-space
and does not pan or zoom with the camera.

Keep product UI in the application. Keep runtime protocol attrs on the elements that
need behavior.

## Build Item Shells

The `:item` slot receives `LivePanels.Item.RenderContext`; the canonical
item is `context.item`:

```heex
<:item :let={context}>
  <article
    class={[
      "window",
      context.selected? && "is-selected",
      context.focused? && "is-focused"
    ]}
  >
    <header {drag_handle_attrs(class: "window-titlebar")}>
      <span>{context.item.label}</span>
      <button {remove_item_attrs(context)}>Close</button>
    </header>

    <main class="window-body">
      {context.content}
    </main>

    <.resize_handles :if={context.view.resizable?} />
  </article>
</:item>
```

Use `context.view` for runtime decisions:

```heex
<button :if={context.view.removable?} {remove_item_attrs(context)}>Close</button>
<.resize_handles :if={context.view.resizable?} />
```

Do not inspect private state or duplicate capability rules in the application.

## Build Toolbars And Inspectors

Toolbars are ordinary application markup with LivePanels attrs:

```heex
<nav class="toolbar">
  <button {add_item_attrs(:note)}>Note</button>
  <button {layout_attrs(:grid)}>Layout</button>
  <button {copy_selection_attrs()}>Copy</button>
  <button {paste_selection_attrs()}>Paste</button>
  <button {undo_attrs()}>Undo</button>
  <button {redo_attrs()}>Redo</button>
</nav>
```

Inspectors can read current projection:

```elixir
defp assign_inspector(socket) do
  selection = LivePanels.Canvas.Read.selection(socket)

  assign(socket,
    selected_count: LivePanels.Canvas.Selection.count(selection),
    history: LivePanels.Canvas.Read.history(socket),
    clipboard: LivePanels.Canvas.Read.clipboard(socket)
  )
end
```

Use application events for product commands, and LivePanels commands for canvas
mutations.

## Respond To Viewport Profiles

Canvas roots expose the active viewport profile and measured viewport metrics:

- `data-livepanels-viewport-profile`;
- `--livepanels-screen-width`;
- `--livepanels-screen-height`;
- `--livepanels-usable-width`;
- `--livepanels-usable-height`;
- `--livepanels-viewport-zoom`;
- `--livepanels-inset-left`, `--livepanels-inset-right`, `--livepanels-inset-top`,
  and `--livepanels-inset-bottom`.

Use `viewport` on the canvas to name profile rules:

```elixir
use LivePanels.Canvas,
  pubsub: MyApp.PubSub,
  viewport: [
    profiles: [
      compact: [screen_width_lte: 720],
      zoomed: [zoom_gte: 1.5]
    ]
  ]
```

The item render context also includes the current viewport snapshot:

```heex
<:item :let={context}>
  <.item_shell
    item={context.item}
    viewport_profile={context.viewport.profile}
  >
    {context.content}
  </.item_shell>
</:item>
```

Use CSS and item markup for presentation changes. Use `viewport_frames` on
viewport item types when the runtime should project a different
screen-pinned frame for a viewport profile. Use canvas item commands when a
world item should change canonical placement.

## Customize Graph UI

Graph ports are application UI:

```heex
<button
  :for={port <- context.ports}
  class={"port port--#{port.direction}"}
  {graph_port_anchor_attrs(port)}
  {if port.direction == :output,
    do: begin_connector_attrs(context.item, port),
    else: complete_connector_attrs(context.item, port)}
>
  {port.label}
</button>
```

Connectors render as connector items in the runtime world SVG projection. Style
the built-in connector classes and data attrs instead of rendering connector SVG
from host code:

```css
.app-canvas .livepanels-connector-path {
  stroke: currentColor;
  stroke-width: 3px;
}

.app-canvas
  [data-livepanels-connector-emphasis="active"]
  .livepanels-connector-path {
  color: var(--app-accent);
}
```

LivePanels owns connector state, endpoint rules, path geometry, labels, markers,
hit targets, and stream replacement. Your application owns what a node, port,
line, label, marker, or selected connector looks like.

## Customize Drawing UI

Drawing controls are application UI:

```heex
<button phx-click={set_cursor_mode_js(@livepanels.canvas_id, :draw, tool: :arrow, style: @draw_style)}>
  Arrow
</button>

<button phx-click={set_cursor_mode_js(@livepanels.canvas_id, :erase)}>
  Erase
</button>
```

The browser owns the current draw mode and draft. The server owns committed
vector items after the drawing command is accepted.

Use a LiveView assign only when your toolbar needs to display the selected
mode, tool, or style.

## Runtime CSS

The package CSS is structural. It provides runtime layer behavior, transforms,
hit areas, resize affordances, and browser hook expectations.

Import it once:

```js
import "livepanels/runtime.css";
```

or:

```css
@import "livepanels/runtime.css";
```

Then add your own CSS for the app. Avoid overriding LivePanels structural
selectors unless you are deliberately taking over that runtime surface.

Runtime-generated browser affordances expose public appearance hooks. Scope
visual treatment under an app-owned root class:

```css
.app-canvas .livepanels-drop-preview { ... }
.app-canvas .livepanels-marquee-rect { ... }
.app-canvas .livepanels-draw-draft-shape { ... }
.app-canvas .livepanels-draw-point-handle { ... }
.app-canvas .livepanels-item-frame.is-livepanels-snapping { ... }
```

Server-rendered helpers accept host classes for visuals that need extra
elements or pseudo-elements:

```heex
<.resize_handles class="app-resize-handle" />
```

The runtime CSS keeps structure, hit targets, transforms, and measurement.
Color, borders, shadows, palettes, and product vocabulary belong to the app.

## Keyboard And Focus

Use keyboard scope attrs where canvas keyboard behavior should apply:

```heex
<main {keyboard_scope_attrs(class: "canvas-shell")}>
  ...
</main>
```

Use focus exemptions inside item frames:

```heex
<button {focus_exempt_attrs()} phx-click="open-inspector">Inspect</button>
```

This lets canvas focus and normal app controls coexist.

Use `input_attrs/1` or the `input` attr on `canvas_runtime/1` when a host needs
to change wheel, pointer, or keyboard bindings:

```heex
<.canvas_runtime
  {canvas_runtime_attrs(assigns)}
  {input_attrs(drag_secondary: :pan, wheel_command: :y)}
/>
```

The same policy can be passed through the JS hook options with
`createLivePanelsCanvasHook({ input: ... })`.

## When Defaults Are Not Enough

Stay on toolkit helpers when you can.

Move to `LivePanels.Toolkit.Integration.ProtocolAttrs` or `LivePanels.Toolkit.Integration.Render`
when you are building a manual render loop, custom SVG substrate, bridge
package, or integration that cannot use `canvas_runtime/1`.

Manual integration APIs are public, but they are not the first path for an
ordinary app canvas.
