Color
Entity color slots
Entities never get a hex in a component — they get a data-attribute slot, and CSS maps each slot to a fg/bg/border triple derived from theme tokens, with unknown entities falling through to neutral.
Reach for it when building
- per-author coloring in collaborative tools
- category and tag theming
- calendar owners and participants
- series or project identity colors
- multi-party conversation views
- color
- theming
- custom-properties
- data-attributes
<div class="ecs-demo">
<p class="ecs-chips">
<span class="ecs-mark" data-slot="0">Editor</span>
<span class="ecs-mark" data-slot="1">Reviewer</span>
<span class="ecs-mark" data-slot="2">Guest</span>
<span class="ecs-mark">Unknown</span>
</p>
<table class="ecs-table">
<tbody>
<tr data-slot="0"><td>Editor</td><td>12 items</td><td>active May</td></tr>
<tr data-slot="1"><td>Reviewer</td><td>31 items</td><td>active August</td></tr>
<tr data-slot="2"><td>Guest</td><td>7 items</td><td>active June</td></tr>
</tbody>
</table>
</div>Colors come from shared theme tokens — --surface, --ink, --border, --accent and friends — so this CSS carries no palette
of its own. Use Runnable file to copy the tokens along with it.
/* The bare attribute selector is the neutral fallback: an element with no
slot value — an unknown or deleted entity — resolves to dimmed defaults
instead of breaking or borrowing slot 0's identity. */
[data-slot] {
--slot-fg: var(--dim);
--slot-bg: var(--surface-2);
--slot-border: var(--border);
}
/* Slots derive from the theme's own hue anchors, so each triple already has
a light and dark answer without any per-theme rules here. */
[data-slot='0'] {
--slot-fg: var(--accent);
--slot-bg: color-mix(in srgb, var(--accent) 12%, var(--surface));
--slot-border: color-mix(in srgb, var(--accent) 35%, transparent);
}
[data-slot='1'] {
--slot-fg: var(--signal);
--slot-bg: color-mix(in srgb, var(--signal) 12%, var(--surface));
--slot-border: color-mix(in srgb, var(--signal) 35%, transparent);
}
[data-slot='2'] {
--slot-fg: var(--ok);
--slot-bg: color-mix(in srgb, var(--ok) 12%, var(--surface));
--slot-border: color-mix(in srgb, var(--ok) 35%, transparent);
}
.ecs-demo { max-width: 480px; }
.ecs-chips { margin: 0 0 1rem; display: flex; gap: 0.5rem; flex-wrap: wrap; }
/* Downstream classes only ever reference the triple. */
.ecs-mark {
display: inline-block;
padding: 0.1rem 0.6rem;
border-radius: 999px;
font-size: 0.9375rem;
font-weight: 650;
background: var(--slot-bg);
border: 1px solid var(--slot-border);
color: var(--slot-fg);
}
/* Chips without any data-slot still need the fallback triple. */
.ecs-mark:not([data-slot]) {
--slot-fg: var(--dim);
--slot-bg: var(--surface-2);
--slot-border: var(--border);
}
.ecs-table {
border-collapse: collapse;
width: 100%;
font-size: 0.9375rem;
}
.ecs-table td {
padding: 0.55rem 0.75rem;
border-bottom: 1px solid var(--border);
color: var(--ink);
}
/* The row spine is an inset box-shadow, not a border: border-collapse would
merge a real border with the row rule. */
.ecs-table td:first-child {
box-shadow: inset 4px 0 0 var(--slot-fg);
font-weight: 600;
color: var(--slot-fg);
}Paste this into an agent to rebuild the pattern from scratch.
Build an entity coloring system where no component ever holds a hex value: an element declares `data-slot="0..N"` and CSS resolves the slot to a triple of custom properties — `--slot-fg` (text and spines), `--slot-bg` (tint fills), `--slot-border` — that every downstream class consumes.
Three layers. First, the bare `[data-slot]` attribute selector sets the triple to neutral values (dimmed text, sunken background, plain border): that is the fallback for an unknown, deleted, or overflowed entity, and it means the failure mode of bad data is a gray chip, not a broken style or a stolen identity. Second, each `[data-slot='N']` rule overrides the triple, deriving all three values from one of the theme's existing hue anchors (accent, signal, success, and so on) with color-mix — fg at full strength, bg at ~12% over the surface, border at ~35% — so every slot automatically has a correct light-theme and dark-theme answer with zero per-theme rules in this file. Third, components (`.mark`, table rows, cards) style themselves only through the triple, never through a slot number.
Assign slots in application code as `(entityId - 1) % slotCount` from a stable id — never from a list index — so an entity keeps its color across re-sorts, filters, and pagination.
One rendering detail worth keeping: in tables with `border-collapse: collapse`, draw the entity's identity spine on the first cell with `box-shadow: inset 4px 0 0 var(--slot-fg)` rather than a left border, which the collapse algorithm would merge with the row rule.
Every color derives from theme custom properties, and each slot triple must stay distinguishable from its neighbors in both light and dark themes.