# Tinyworld Block Button Style

> Use when styling any tool/block button, palette tile, icon chip, or new clickable square in Tiny World Builder. The locked-in "block" aesthetic — a raised square with a dark category-colored outline, an inner white line, and a white-bodied glyph outlined in the same dark color. Apply this whenever a new icon button/tile is added so the UI stays consistent.

- Skill: `jasonkneen/tinyworld-block-button-style` (Agent Skill)
- Install (CLI): `npx skillmds@latest add jasonkneen/tinyworld-block-button-style`
- Raw SKILL.md: https://api.skillmd.com/api/skills/jasonkneen/tinyworld-block-button-style/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: jasonkneen (https://skillmd.com/u/jasonkneen)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/jasonkneen/tinyworld-block-button-style

---


# Tiny World "Block" button style (locked design language)

The canonical look for tool/block buttons (the floating palette `.tool-palette`,
the group popout `.flyout`, and the bottom toolbar `.tool` blocks). Jason signed
off on this as the house style — reuse it for any new icon square/tile.

## The three ingredients

1. **Outlined square** — a `1.5px` border in a *darkened* version of the tile's
   category color, an inner white line, and a slight raised lift.
2. **Outlined glyph** — a white icon body with a matching dark colored outline,
   drawn with `paint-order: stroke` + `vector-effect: non-scaling-stroke` so one
   `stroke-width` looks identical across every glyph viewBox (24, 512, …).
3. **Category color** — the dark outline is the darkened category tint; the
   square keeps a faint translucent category background.

## Category palette

| posType   | background tint            | dark outline (border + glyph stroke) |
|-----------|----------------------------|--------------------------------------|
| terrain   | `rgba(123,194,48,0.16)`    | `#3a6511`                            |
| primary   | `rgba(23,107,235,0.13)`    | `#1e428a`                            |
| tertiary  | `rgba(254,146,14,0.18)`    | `#a05600`                            |
| shield    | `rgba(45,215,255,0.15)`    | `#0e5f7e`                            |
| neutral   | (none)                     | `#2f3b57` (default `--glyph-outline`)|

The dark outline = the category tint darkened ~50–60%. For a new category,
pick the same hue at roughly that lightness.

## Recipe (lives in `styles/tiny-world.css`)

Glyph (white body + colored outline):

```css
.tool .tool-glyph { color: #fff; --glyph-outline: #2f3b57; }
.tool .tool-glyph svg { fill: currentColor; overflow: visible; }
.tool .tool-glyph svg * {
  fill: currentColor;
  stroke: var(--glyph-outline);
  stroke-width: 2.1;
  vector-effect: non-scaling-stroke;
  paint-order: stroke;
  stroke-linejoin: round;
  stroke-linecap: round;
}
/* per posType: set --glyph-outline on the .tool-glyph */
```

Raised, outlined square (per posType; same pattern for `.tool` and
`.tool.flyout-tool[data-pos-type]`):

```css
.tool[data-pos-type="primary"] {
  border: 1.5px solid #1e428a;                 /* dark category outline */
  box-shadow:
    inset 0 0 0 1.5px rgba(255,255,255,0.92),   /* inner white line */
    inset 0 -3px 0 rgba(30,66,138,0.22),        /* bottom bevel */
    0 2px 3px -1px rgba(20,30,50,0.22);         /* raised drop shadow */
}
```

## Applying it to a new button/tile

- Give the tile a `data-pos-type` (terrain/primary/tertiary) — the existing
  rules then paint it automatically. `posTypeForTool()` in
  `19-tools-toolbar.js` maps a tool to its posType.
- Bottom-toolbar buttons must all carry a stable `data-pos-type`: Select uses
  `primary`, Erase uses `neutral`, and inactive group buttons use
  `posTypeForToolGroup(group)` so their borders do not disappear until a group
  becomes active. The late `Unified block buttons` CSS block applies the same
  raised border/inner-line treatment to `.toolbar .tool`,
  `.toolbar .tool-group-btn`, `.flyout .tool.flyout-tool`, and the floating
  `.tool-palette`; keep that block after generic flyout sizing rules.
- Selection radial buttons stay circular, but they still use the same category
  contract. `33-radial-menu.js` passes `data-pos-type` through `makeBtn()`
  (root actions = `primary`, close/back = `neutral`, reset swatch = `neutral`).
  Close/Back sits as the compact center button (`.radial-center`), with action
  buttons on a tight orbit; do not put close/back back into an outer top slot or
  expand the ring radius. The mooring style radial in `36-mooring-interaction.js`
  uses `tertiary`. Style them via `.radial-btn[data-pos-type]`, not by returning
  to pale generic circles.
- Radial labels/icons need a separate high-contrast `--radial-ink` on an
  opaque `--radial-bg`. Do not use very transparent category fills for radial
  buttons; the 3D scene bleeds through and makes the icon text unreadable.
- Appbar icon buttons, the single language picker trigger, and the left side-rail
  controls are also category chrome now. Keep `data-pos-type` on those static
  HTML buttons and style them through `.appbar .btn.icon[data-pos-type]`,
  `.controls .btn.icon[data-pos-type]`, and `.language-trigger[data-pos-type]`.
  The language choices live in the upward menu; do not put the four-flag strip
  back in the appbar.
- The top-center `.world-pill[data-pos-type="primary"]` keeps the primary blue
  outline, but its resting fill is deliberately bright white glass
  (`rgba(255,255,255,0.82)`) rather than the generic blue primary tint so the
  world selector matches the whiter chrome surfaces. Its hover state must keep
  `transform: translateX(-50%)`; do not apply the generic hover lift to this
  fixed centered pill or it jumps.
- The token corner is deliberately plain text, not a pill panel. Keep
  `.token-corner` free of `data-pos-type`, borders, backdrop blur, and hover
  transforms; only the nested GitHub link uses
  `.token-corner .btn.icon[data-pos-type]` block-button chrome.
- Build the icon with `buildToolButton(tool, { flyout: true })` so it reuses the
  `.tool-glyph` / `.tool-icon` machinery and the outline rules.
- Bottom-toolbar utility buttons use `buildToolbarUtilityButton(...)` and the
  same `.tool icon-only` block square. Keep Home and Shield as adjacent utility
  buttons at the front of the bottom toolbar; Shield uses `data-pos-type="shield"`
  and reflects the raised/lowered `VoxelShield` state with `.active` and
  `aria-pressed`.
- Active/pressed toolbar states must override dark/after-hours theme rules: keep
  the category outline color, use a light raised fill, a visible 2px outer ring,
  and a short bottom accent bar so selected tools do not read like pale disabled
  buttons. Because the theme blocks use `body.ui-theme-dark ... !important`,
  add matching `body.ui-theme-dark` active selectors after the generic selected
  block; a lower-specificity late rule is not enough.
- If a component hard-codes button `background`, `color`, `border`, or local
  category variables (for example launch/game-type, auth, account, or corner
  chrome buttons), add a component-scoped `body.ui-theme-dark ...` rule after the
  light rule. Do not rely on inherited `--ink`: `background: var(--ink)` plus
  `color: #fff` becomes unreadable when dark mode flips `--ink` to light text.
- Light-mode selected option chips/tabs in the selection and Layers properties
  panels are part of the same glass language. Keep their old translucent fills
  (`rgba(60,130,247,0.15)` for active chips, `rgba(23,107,235,0.14)` for active
  Layers tabs) plus backdrop blur; do not replace them with solid white fills.
- Mono/utility icons (select, erase) use `.tool-icon` and render as
  `fill:none; stroke:currentColor` outlines (the rule is shared between
  `.toolbar`, `.flyout`, and `.tool-palette`). Keep them line-art, not filled.
- Never go back to solid-filled (`fill:currentColor; stroke:none`) glyphs for
  block buttons — that is the *old* look this style replaced.

## Gotchas

- `vector-effect`/`paint-order` must be on the path/shape elements
  (`svg *`), not the `<svg>` — they don't inherit.
- The global `[data-tooltip]{position:relative}` rule can clobber `position` at
  tied specificity; if a styled button mispositions, raise selector specificity
  (e.g. `button.foo`).
- Verify in a real browser, not synthetic events: sample the rendered pixels
  (a primary block reads dark border `30,66,138` → white line `~250` → tint).

