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".
A die is the exception, and a useful one. A die has no art for the fill to
sit behind, so backgroundColor is the die’s face colour — the single-
component control is labelled Die colour there, and it is how you colour
dice without uploading any art. See dice. (The multi-select panel
keeps the Background label whatever kinds are selected; it writes the same
field.)
| Field | Values | Default | What it does |
|---|---|---|---|
backgroundColor | hex colour string (#rrggbb) | .optional() | Solid fill painted behind the art, visible only through transparent areas — on a die, the face colour itself |
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, token and sheet is flippable; every card,
piece and token is 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.
The die is the one exclusion, and deliberately: a die has N numbered faces, not two sides, so it offers Roll instead of Flip and is never rotated (its result, not its orientation, is what it carries). A sheet is the mirror case — it flips, so a player can turn a filled-in pad over to hide it, but there is nothing to gain from turning a sheet of paper sideways, so it offers no Rotate.
| Field | Values | Default | What it does |
|---|---|---|---|
flippable | true | .optional() | Legacy flag. Every card/piece/token/sheet is flippable regardless — a die never is — so 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 |
How attached markers lay out — attachDisplay
Set on the host (the piece being attached to), this decides what several markers on one host look like:
stack(default) — identical markers collapse into a single chit with a×Ncount badge. Right for counters where only the TOTAL matters: three +1/+1 counters on a creature read as one chit showing ×3.free— every marker stays exactly where the player dropped it, stored as a fractionalattachOffseton the object. Right when a marker's POSITION on the piece carries meaning — beetles placed on particular corners of a field card, damage pips on a specific ship section.
A named attachSlots entry always wins over either mode: a marker that lands
in a slot sits at the slot’s coordinates regardless of attachDisplay.
Free placement has no capacity limit — a host holds as many markers as players drop on it. (Slots do cap, but overflow still attaches; it falls back to the anchor rather than being refused.)
| Field | Values | Default | What it does |
|---|---|---|---|
attachDisplay | stack | free | stack | How markers attached to this component are positioned |
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.
A statBlock does double duty on a sheet (kind: sheet): a
sheet holds no values of its own, so every blank on the printed pad is one of
these declared properties, and the sheet field only says how they are laid out
in rows and columns. That is why the Studio’s sheet builder writes both lists at
once — a number field becomes a number cell, a toggle a checkbox.
| 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
- Sheets — a printed pad whose blanks are
statBlockproperties - Tokens — the most common home for a
valueBindingcounter - Pieces — meeple silhouettes via
shape: polygon - Grids — the cells a
footprintcovers