Skip to content

Getting started ​

Put one themed field in a Lit element and keep its value in the element's state.

1. Register the elements ​

ts
import "@jolly-pixel/ui/elements/number";
import "@jolly-pixel/ui/elements/scope";
  • Each element has its own entry point, named after its tag without jolly-. Importing it registers the element and the elements it renders.
  • Import an element from the module that renders it. The root entry point, @jolly-pixel/ui, registers none: it exports the types, the theme and the helpers.
  • lit is a peer dependency. The application and the package must share one copy.

2. Add a theme scope ​

html
<body>
  <jolly-scope>
    <brush-panel></brush-panel>
  </jolly-scope>
</body>

Elements take their colours, spacing and font from tokens that jolly-scope declares. Content outside a scope renders without them. Theming and density covers the scope options.

3. Render a field and write its value back ​

ts
import { LitElement, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import type { JollyChangeDetail } from "@jolly-pixel/ui";

@customElement("brush-panel")
export class BrushPanel extends LitElement {
  @state()
  declare size: number;

  constructor() {
    super();
    this.size = 4;
  }

  override render() {
    return html`
      <jolly-number
        label="Size"
        min="1"
        max="64"
        .value=${this.size}
        @jolly-change=${this.#onSize}
      ></jolly-number>
    `;
  }

  #onSize = (event: CustomEvent<JollyChangeDetail<number>>) => {
    this.size = event.detail.value;
  };
}
  • value is a property, with no attribute: bind it with .value.
  • The field shows value and reports edits. It never keeps an edit itself: if the handler does not write the value back, the next render shows the old value again.
  • jolly-change is a committed edit, such as Enter, blur or a released slider. jolly-input reports the edit in progress: each step of a drag, or each keystroke in a jolly-text.
  • Both events bubble and cross shadow roots, so one listener on a container hears every field below it.
  • While a text-based field is focused, its draft wins over a new value, so an edit from elsewhere cannot rewrite text under the caret. Escape discards the draft.

Next steps ​