packages/components — measured on 0.2.321. Related to #1993 but separable: that one is about components that hard-code Tailwind palette colours, this one is about the components that correctly don't.
The problem
The library styles itself with its own semantic colour names. Counted across all 102 components:
text-fg 76 bg-surface 19
text-fg-strong 26 ring-line-strong 16
text-fg-muted 26 bg-surface-sunken 15
border-line 24 bg-surface-raised 15
text-fg-soft 21 border-line-strong 14
text-fg-subtle 19 divide-line 8
text-accent 7 ring-accent 7That is a good vocabulary, and using it is the right call. But nothing in the package defines those names, and nothing documents them. They are not in the README, not in a shipped stylesheet, and not stated as a contract an app is expected to satisfy.
So an app that installs the library and drops in a component gets markup carrying class names that resolve to nothing. The result is not an obvious failure — it is a half-styled component. <Table> renders with divide-y applying a border width while divide-line supplies no colour. <Skeleton> renders with no background at all, because its entire fill is bg-surface-sunken. Text falls back to inherit. It looks like the component is broken rather than like the app is missing a definition.
We found this the slow way: adopting components, seeing them render wrong, and eventually grepping the library's source to extract the list above.
What would fix it
Any one of these, roughly in order of how useful they'd be:
- Ship a default stylesheet defining all of them against sensible values, which an app can override. Then a fresh install works out of the box and theming is opt-in rather than mandatory.
- Define them as CSS custom properties with fallbacks —
color: var(--stx-fg, #111)— which composes with #1993's suggestion and makes both problems one fix. - Document the contract: a table in the README saying "define these N utilities, or these N custom properties, in your theme". Cheapest option, and it would have saved us the reverse-engineering.
For reference, what adopting it took us
We alias them onto each app's existing palette in the crosswind config:
colors: {
surface: 'var(--panel)',
'surface-sunken': 'var(--bg)',
'surface-raised': 'color-mix(in srgb, var(--accent) 8%, var(--panel))',
fg: 'var(--text)',
'fg-strong': 'var(--text)',
'fg-muted': 'var(--text-2)',
'fg-soft': 'var(--text-2)',
'fg-subtle': 'var(--text-3)',
line: 'var(--border)',
'line-strong': 'color-mix(in srgb, var(--text-3) 55%, var(--border))',
'accent-solid': 'var(--accent)',
}Eleven lines per app, five apps. After that the colour-neutral components render correctly in each app's own design, in both light and dark, with no className at any call site — which is exactly how it should work, and it would be nice if it worked that way without each adopter deriving the list from your source first.
One note in case it is useful: divide-line is the one name we cannot satisfy, because crosswind generates no divide-* colour utility at all. That makes Table's divide-y divide-line inert for us — harmless, but worth knowing that the divider is silently absent rather than mis-coloured.