DocsComponents

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 Shape control sits in the same Properties section — Rectangle, Circle or Hexagon (a generated custom polygon can be reset to a rectangle there too). The remaining refinements — 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.

Size — where a component’s proportions come from

A component’s Fixed size (Width / Height / Thickness in the inspector) is optional, and the badge on that section tells you which rule is in play:

  • auto — the component has no size of its own, so it takes its proportions from its artwork and its scale from the zone it sits in. Square art is drawn square, a tall card is drawn tall. This does not depend on whether you called the component a card, a piece or a token, so changing its Type never changes its shape.
  • 63×88 (any explicit size) — those proportions win, and the artwork is fitted inside them. If the art’s proportions differ you will see the table through the gap, because art is never cropped. Clear all three fields to go back to auto.

taybl adds no corners to your artwork either. Whatever outline you drew is what gets dealt — a card with square corners stays square, and one you drew with rounded corners keeps exactly the curve you gave it. The rounding you see on a placeholder taybl renders for you — a colour-and-letter chip, a dice face with pips, an empty pad — is taybl’s own, and it goes away the moment you bind artwork.

Either way the number is a ratio, not a measurement: most zones scale their contents to the zone box, so size sets proportions and relative scale rather than the absolute size on screen.

One place a fixed size always shows through is dragging. A component with one is carried at that size — the size it occupies out on the open table — rather than at whatever size the zone you picked it up from happened to draw it, so a piece pulled out of a tightly packed supply, or a card out of your hand, previews the drop instead of the pickup. An auto component is carried at the size it was drawn at, since it has no size of its own to fall back on.

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.

typeExtra fieldsWhat it draws
rect—A rectangle (the default)
circle—A circle with diameter min(w, h). Applies to the colour fallback — uploaded art is never clipped to it, so a round piece wants round art
hexorientation: pointy or flatA hexagon, point-up or flat-top
polygonpoints: 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.)

FieldValuesDefaultWhat it does
backgroundColorhex 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".

FieldValuesDefaultWhat it does
paddingnumber 0..0.45 (fraction inset per side).optional() (fills the cell)Breathing room around the art inside its cell

Preview size — how big it peeks

Resting the pointer on a component, or holding V over it, opens a peek: the same component, drawn large enough to read. The peek always keeps the real artwork — its true proportions and its shape outline, so a hex tile peeks as a hexagon and a round token peeks round. This setting says how BIG it is drawn, and nothing else.

Set previewSize when the default is the wrong size for what the art has to say. A dense player aid or a card of rules text wants large; a wooden cube that is legible at a glance wants small, so its peek does not blot out half the table. The value is the peek’s longest edge: the named sizes are small 320px, medium 480px and large 720px, and a plain number (120–1200) sets it exactly. Whatever you pick is clamped to fit the player’s screen, so a big value can never open a peek off the edge.

Edit it in the Layout tab: select a component and open Preview size, whose Peek size menu offers Small, Medium, Large and Custom… — the last revealing a Longest edge box for an exact number of pixels. Or ask Pip to "make the player aid preview large". The peek itself belongs to the play surface, so check your choice in Preview rather than on the editor canvas.

How long the pointer has to rest before a peek opens is the player’s choice, not the designer’s — it lives in their account preferences, applies to every game they play, and can be switched off altogether. So size the peek for the reader who opens one, but never write a rule that assumes one opens by itself. See moving around the table.

FieldValuesDefaultWhat it does
previewSizesmall | medium | large | number 120..1200 (px).optional() (medium, 480px)Longest edge of the component’s peek

Layer — what sits on top

Where two objects overlap, one of them has to be drawn over the other, and normally that is simply whichever was touched last. For cards that is exactly right. For markers it is the wrong way round: put a cube on a card to mark it, move the card, and the cube disappears underneath the thing it was marking. layer settles the question once, in the design, instead of leaving it to whatever a player happened to pick up most recently.

Higher paints on top; absent → 0. It is a ceiling, not a nudge — a layer-0 card can never rise above a layer-1 cube, however recently it was moved. That is the whole point of the field: give your markers a layer above your cards and they stay visible on the things they mark, permanently, with nobody having to remember to restack them.

Within one layer nothing changes. The last thing touched still sits on top, so a deck of ordinary cards that all share a layer goes on behaving exactly as it always has — you only notice the field where you have deliberately put things on different layers.

It applies only where pieces sit loose: the open play felt, and any free or scatter zone. A deck, a fan, a grid and a board cell each order their own contents — their arrangement already says what sits where — so they ignore it.

This is also what bounds the table’s Bring to front / Send to back verbs (and the ] / [ keys): they reorder within a layer, so neither will lift a card above a higher-layer object, and the menu offers them only when they can actually move something. See handling components.

Edit it in the Layout tab: select a component and open Layer, whose Layer box takes the number directly — with Bring to front and Send to back buttons beside it that do the arithmetic for you, setting this component just past the highest or lowest layer anything else in the game uses. Or ask Pip to "put the cubes above the cards".

FieldValuesDefaultWhat it does
layerinteger.optional() (0)Draw order among overlapping objects in open play and free / scatter zones — higher paints on top, and a lower layer can never rise above a higher one

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.

FieldValuesDefaultWhat it does
flippabletrue.optional()Legacy flag. Every card/piece/token/sheet is flippable regardless — a die never is — so this no longer gates flipping
rotatabletrue.optional()Legacy flag. Every card/piece/token is rotatable regardless — this no longer gates rotation
stackabletrue.optional()Copies can be stacked into a pile
rotationStepnumber > 0 (degrees).optional()The rotation snap increment (e.g. 90 → four orientations). Absent → free rotation. Ignored whenever orientations is present
orientationsarray 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.

FieldValuesDefaultWhat it does
states[].idtext (snake_case)—Unique state identifier within this component
states[].nametext.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[].propertieskey/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.

FieldValuesDefaultWhat it does
attachabletrue.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.

FieldValuesDefaultWhat it does
attachSlots[].idtext (snake_case)—Unique slot identifier within this component
attachSlots[].labeltext.optional()Display name shown on hover
attachSlots[].x0–1—Horizontal position as a fraction of the host width
attachSlots[].y0–1—Vertical position as a fraction of the host height
attachSlots[].acceptsarray of kinds.optional()Which component kinds may attach here (omit = any)
attachSlots[].capacityinteger > 01Maximum 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 ×N count badge. Right for counters where only the TOTAL matters: three +1/+1 counters on a creature read as one chit showing ×3. Dragging that chit takes one marker off, not the whole pile, so a player can spend a counter at a time.
  • free — every marker stays exactly where the player dropped it, stored as a fractional attachOffset on 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.
  • underlay — each attachment is drawn beneath the host at full size, keeping its own rotation and face, so only the part that overhangs is visible. This is a physical tuck: a card slid under a tableau card so a scoring strip pokes out, a wonder stage under a wonder board, a card installed under an ice. Like free it positions each attachment individually — an underlay cannot collapse into a ×N chit, which is the whole point of it.

Tucking at the table

Players tuck by holding Alt (Option) while dragging one object onto another, or from the object menu — Tuck under…, with Un-tuck on the tucked object afterwards. Tab cycles the drop mode mid-drag — and, over a space with landing rules, the rules themselves — the drag ghost shows which mode is armed, and a live preview of the tuck appears under the host before you let go.

The modifier is required when the host is a card, and deliberately so: without it, dropping a card onto an occupied tableau cell could never be told apart from tucking it underneath. A drop with no modifier always means what it always has.

A tuck is an ordinary attachment underneath, so it follows its host when the host moves, and is captured or removed along with it.

A face-up underlay is public. Only a strip of it shows on screen, but the card is face-up in a public zone, so its identity is visible to every player exactly as any other face-up card would be. Tuck face-down when the tucked card is meant to be secret — that redacts properly, and opponents see only that something is tucked there, at its real size, showing its back.

A named attachSlots entry always wins over any of the three modes: 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.)

FieldValuesDefaultWhat it does
attachDisplaystack | free | underlaystackHow things attached to this component are positioned — pinned on top as a counted chit, kept where they were dropped, or tucked underneath at full size

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.

FieldValuesDefaultWhat it does
scopeglobal player—Whether the number lives on the table or on the owning player
keytext—Which state value to read and display
displaynumber dial.optional()A numeric badge (default) or a dial widget
minnumber.optional()Display-only lower bound for the spinner — not enforced
maxnumber.optional()Display-only upper bound for the spinner — not enforced
stepnumber > 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.

FieldValuesDefaultWhat it does
statBlock[].keytext—The property key this value lives under
statBlock[].labeltext.optional()Badge label; absent → the key
statBlock[].typenumber bar text togglenumberValue kind + control shape
statBlock[].icontext.optional()Curated glyph key; unknown → 2-letter badge
statBlock[].pinnedbool.optional()Show inline on the piece (capped at statStripMax)
statBlock[].initialnumber/text/bool.optional()Value seeded onto each copy; the reset default
statBlock[].minnumber.optional()Display-only lower bound — not enforced
statBlock[].maxnumber.optional()Display-only upper bound / bar ceiling — not enforced
statBlock[].stepnumber > 01−/+ increment size — display hint only
statBlock[].displaynumber dial.optional()A numeric badge (default) or a dial
  • Components overview — the shared fields and kinds
  • Sheets — a printed pad whose blanks are statBlock properties
  • Tokens — the most common home for a valueBinding counter
  • Pieces — meeple silhouettes via shape: polygon
  • Grids — the cells a footprint covers
Sign in to ask Pip about building games.
Sign in