Skip to content

Your first component

A Gesso component is a function. It is called once, and what it returns is a tree of nodes that stays. Nothing here re-runs when the count changes: the text prop is bound to an Observable, so one property on one node is written and the next frame is drawn from it.

The counter above is running in a render worker on this page. Its source is the whole example, and the same file is what the test suite asserts on:

tsx
import { percent } from 'gesso-core';
import { Button } from 'gesso-components';
import { type ComponentContext, type Inputs, computed, input, internalState } from 'gesso-framework';

export function Counter(inputs: Inputs<{ label?: string }>, _context: ComponentContext) {
  const label = input(inputs.label, 'Count'); // inputs are cells; this one has a default
  const count = internalState(0);
  const caption = computed(() => `${label.value}: ${count.value}`);

  return (
    <row gap={12} x="center" y="center" width={percent(100)} height={percent(100)}>
      <text text={caption} textStyle="title" />
      <Button label="Add one" onClick={() => count.value++} />
    </row>
  );
}

Two things in it are worth naming. input(inputs.label, 'Count') gives an optional prop a default while keeping it a cell, so a parent that later changes the label still reaches this node. And internalState(0) is state the component owns; writing count.value++ marks exactly the bindings that read it.

Notice what is not in it. There is no colour: not a hex value, not even a palette name, because a Button reads the theme's control tokens and text takes its colour from the type scale, so the counter follows this site's light and dark toggle without knowing that either exists. Light and dark shows the root component that arranges it.

There is no size either. textStyle="title" names a role in the theme's type scale rather than a font size, a weight and a line height, and the button's padding comes from the theme's spacing scale rather than from four numbers on the element. Tokens are the idiom throughout this site: a number or a colour written on an element is the exception, and usually means a role is missing from the scale.

<Button label="Add one" /> is one prop for two jobs. It is the words on the face of the button and the name an assistive technology reads, because in a button that has words on it those are the same thing. The Button page has the rest.

Next

Components run once is what "called once" means for where state and derived values go.