Shapes, behaviors & value bindings
Beyond the kind-specific fields, every component — piece, card, die, token — shares a set of optional rendering refinements. Each one defaults to today’s plain behaviour when absent, so you only add them when an object needs a non-rectangular outline, snap rotation, an attached marker, or a live number.
These are still table-as-scene fields: they change how an object is drawn
and handled, not what is legal. In particular a value’s min / max /
step are display hints — taybl never clamps the number.
How to edit in the Studio
Open your game → Layout tab and select the component. The Behaviors
section exposes the Flippable, Rotatable, and Stackable toggles.
The finer refinements — shape, footprint, rotationStep / orientations,
attachable, and valueBinding — are produced by the generation pipeline
into the GDL and don’t have a dedicated inspector control yet; adjust them by
editing the generated definition.
Shape — the outline within the size box
A component’s shape is the outline drawn inside its size bounding box —
it sets the hit-area, the colour fallback, and how tiles tessellate. It is a
discriminated union on type. Absent → rect.
type | Extra fields | What it draws |
|---|---|---|
rect | — | A rectangle (the default) |
circle | — | A circle with diameter min(w, h) |
hex | orientation: pointy or flat | A hexagon, point-up or flat-top |
polygon | points: array of { x, y } (≥ 3) | A free outline; each point is 0..1 of the size box (0,0 = top-left, 1,1 = bottom-right) — used for meeple silhouettes |
Background colour — a surface behind transparent art
Card and component art is often a transparent PNG. By default nothing is
painted behind it, so the transparent areas show the table felt. Set
backgroundColor to a hex colour (e.g. #ffffff) and taybl paints that solid
fill behind the art — it shows only through the transparent pixels, giving a
clear card asset a proper white surface. An opaque asset simply covers it, so
the fill never appears; absent → no fill (transparent art shows the table).
Edit it in the Layout tab: select a component and use the Appearance → Background colour picker (with a clear button to remove it). Select many components to set them all at once — the multi-select panel has the same control — or ask Pip to "give all my cards a white background".
| Field | Values | Default | What it does |
|---|---|---|---|
backgroundColor | hex colour string (#rrggbb) | .optional() | Solid fill painted behind the art, visible only through transparent areas |
Padding — breathing room inside the cell
By default a component fills its cell edge-to-edge, exactly like an uploaded
image. Set padding to inset the art — it is a fraction (0..0.45) trimmed
from each side, so 0.1 leaves a 10% margin all around and pulls the piece
in. Use it to give a die, meeple, or token a little air in a large cell, or
leave it at 0 for tiles that should tessellate. Board tiles laid on a
tessellating lattice ignore padding and always touch.
Edit it in the Layout tab: select a component and drag the Padding slider — or ask Pip to "add some padding to the dice".
| Field | Values | Default | What it does |
|---|---|---|---|
padding | number 0..0.45 (fraction inset per side) | .optional() (fills the cell) | Breathing room around the art inside its cell |
Footprint — multi-cell pieces
A footprint lets one component cover several grid cells (a domino, a
polyomino, a 2×2 building). It is { cells: { col, row }[] } — integer
offsets from the anchor cell, which is the cell a placement or move
names. The anchor { col: 0, row: 0 } must be present; the covered cells
are derived at render time, so no runtime state changes. Absent → the
component occupies a single cell.
Behaviors — how a component can be handled
Every card, piece, and token is flippable and rotatable. The physical
table lets you turn any bit over or spin it, so these are not opt-in: a
component flipped to a back with no art shows a generic back (a neutral
felt square with a ring, matching a card’s back), and a rotate with no snap
config cycles freely. The behaviors object only narrows or annotates
these affordances. flippable/rotatable are kept for legacy definitions but
no longer gate anything, so the inspector no longer shows them as checkboxes;
stackable is still an opt-in checkbox.
| Field | Values | Default | What it does |
|---|---|---|---|
flippable | true | .optional() | Legacy flag. Every card/piece/token is flippable regardless — this no longer gates flipping |
rotatable | true | .optional() | Legacy flag. Every card/piece/token is rotatable regardless — this no longer gates rotation |
stackable | true | .optional() | Copies can be stacked into a pile |
rotationStep | number > 0 (degrees) | .optional() | The rotation snap increment (e.g. 90 → four orientations). Absent → free rotation. Ignored whenever orientations is present |
orientations | array of angles in degrees | .optional() | Explicit allowed angles (e.g. [0, 90, 180, 270]). When present this is the full set of legal angles and overrides rotationStep. Each entry is wrapped into [0, 360) and de-duplicated |
States — multi-state components
A states array gives one component 2+ switchable states, each with its
own art and properties — a tile that upgrades village → city → metropolis, a
creature that transforms, a board with several printed sides. The runtime
object remembers which state is active and a player flips between them with
the switch state action; the renderer draws the active state’s face.
The first state is the default. This is not the same as a card’s
variants, which expand into separate cards — states keep one object whose
face changes. Absent → a single-state component, exactly as before.
| Field | Values | Default | What it does |
|---|---|---|---|
states[].id | text (snake_case) | — | Unique state identifier within this component |
states[].name | text | .optional() | Display name for the state |
states[].faces | { front, back } bindings | .optional() | Art for this state; falls back to the component’s own faces |
states[].properties | key/value record | .optional() | Property overrides applied while this state is active |
Attachable — pinned markers
attachable: true lets a component be pinned onto another object instance
— a +1/+1 counter on a creature, a damage marker on a card. A pinned marker
follows its host when it moves and is removed or captured with it; the engine
handles that ripple, and the renderer draws the marker as an overlay on the
host. Absent → not attachable.
| Field | Values | Default | What it does |
|---|---|---|---|
attachable | true | .optional() | This component may be pinned onto another object instance |
Attach slots — named positions on a host
When a host component declares attachSlots, markers that attach to it can
target a specific slot instead of the generic anchor (corner / center / edge).
Each slot has fractional x / y coordinates within the host's bounding box
(0,0 = top-left, 1,1 = bottom-right), an optional label, an accepts filter,
and a capacity (default 1). The renderer draws the marker centred at the
slot's position. When no slotId is provided on attach, the engine auto-assigns
the first slot with available capacity.
| Field | Values | Default | What it does |
|---|---|---|---|
attachSlots[].id | text (snake_case) | — | Unique slot identifier within this component |
attachSlots[].label | text | .optional() | Display name shown on hover |
attachSlots[].x | 0–1 | — | Horizontal position as a fraction of the host width |
attachSlots[].y | 0–1 | — | Vertical position as a fraction of the host height |
attachSlots[].accepts | array of kinds | .optional() | Which component kinds may attach here (omit = any) |
attachSlots[].capacity | integer > 0 | 1 | Maximum markers this slot holds |
Value binding — a live number on a component
A valueBinding renders a runtime number on the component — a life total, a
money count, a dial. The renderer reads key from either global or the
owning player’s state. As with every taybl field, the bounds are advisory:
min / max / step shape the spinner widget but the engine does not
enforce them. Absent → no number is drawn.
| Field | Values | Default | What it does |
|---|---|---|---|
scope | global player | — | Whether the number lives on the table or on the owning player |
key | text | — | Which state value to read and display |
display | number dial | .optional() | A numeric badge (default) or a dial widget |
min | number | .optional() | Display-only lower bound for the spinner — not enforced |
max | number | .optional() | Display-only upper bound for the spinner — not enforced |
step | number > 0 | .optional() | Spinner increment size — display hint only |
Stat block — per-instance properties a player adjusts
Where a valueBinding shows one table- or player-scoped number, a
statBlock gives a component an ordered set of per-instance properties — a
creature card with HP, MP, Attack, and Defense, a relic with Charges and an
Element. Each declared property’s initial is seeded onto every copy when it
is placed and is also the default the play menu resets to; from then on
each copy tracks its own value: damage one Hero and the others are
untouched. Every property has a type — a number stepper, a bar (with a
max ceiling and a fill), a short text value, or an on/off toggle — and
an optional icon. Pinned properties show inline on a frosted strip welded
to the piece’s art (capped at the table’s statStripMax); the rest collapse
into a +N pill that opens the stat menu. Players can also add an ad-hoc
property (say crit) to a single card at the table. Values live in the
object’s property bag, so a face-down or hidden card shows nothing. Edit
properties in the Layout tab’s Properties inspector section. As
everywhere, min / max / step are display hints — the engine never clamps
a value. Absent → no properties.
| Field | Values | Default | What it does |
|---|---|---|---|
statBlock[].key | text | — | The property key this value lives under |
statBlock[].label | text | .optional() | Badge label; absent → the key |
statBlock[].type | number bar text toggle | number | Value kind + control shape |
statBlock[].icon | text | .optional() | Curated glyph key; unknown → 2-letter badge |
statBlock[].pinned | bool | .optional() | Show inline on the piece (capped at statStripMax) |
statBlock[].initial | number/text/bool | .optional() | Value seeded onto each copy; the reset default |
statBlock[].min | number | .optional() | Display-only lower bound — not enforced |
statBlock[].max | number | .optional() | Display-only upper bound / bar ceiling — not enforced |
statBlock[].step | number > 0 | 1 | −/+ increment size — display hint only |
statBlock[].display | number dial | .optional() | A numeric badge (default) or a dial |
Related
- Components overview — the shared fields and kinds
- Tokens — the most common home for a
valueBindingcounter - Pieces — meeple silhouettes via
shape: polygon - Grids — the cells a
footprintcovers