Use when adding new error messages to React, or seeing "unknown error code" warnings.
npx skills add berhalak/skills --skill "grist-ui-design"
Install specific skill from multi-skill repository
# Description
Use when building or styling UI components in the Grist codebase β cards, lists, icons, badges, accordions, typography, spacing, hover effects, or any styled component. Also use when unsure which design tokens, CSS patterns, or component conventions to follow in Grist.
# SKILL.md
name: grist-ui-design
description: Use when building or styling UI components in the Grist codebase β cards, lists, icons, badges, accordions, typography, spacing, hover effects, or any styled component. Also use when unsure which design tokens, CSS patterns, or component conventions to follow in Grist.
Grist UI Design Reference
Overview
Grist uses grainjs styled() for all components. Design tokens come from theme.* (colors) and vars.* (typography/spacing). Never hardcode colors β always use tokens. Modifier classes use the cls("-modifier") pattern.
import { theme, vars } from "app/client/ui2018/cssVars";
import { icon } from "app/client/ui2018/icons";
import { styled } from "grainjs";
Color Tokens (theme.*)
| Token | Use |
|---|---|
theme.text |
Primary text |
theme.lightText |
Secondary/muted text, hints, timestamps |
theme.inputBorder |
Borders, dividers, card outlines |
theme.pageBg |
Page/section background (off-white) |
theme.mainPanelBg |
Left/side panel background |
theme.controlFg |
Links, anchor text (green) |
theme.controlPrimaryBg |
Primary green β icons, active states |
theme.controlSecondaryFg |
Hover border for cards |
theme.selectionOpaqueBg |
Light green tint β icon bg, selected state |
theme.errorText |
Error red |
Exception β hardcoded tints (only for status/semantic use):
background: rgba(208, 2, 27, 0.08); // error bg tint
background: #FFF3E0; color: #E65100; // warning (unsaved)
background: #e8f5e9; color: #2e7d32; // success badge
Typography (vars.*)
| Token | Use |
|---|---|
vars.xsmallFontSize |
Monospace payloads, pill badges |
vars.smallFontSize |
Labels, hints, sub-text |
vars.mediumFontSize |
Default body text |
vars.xxxlargeFontSize |
Page headings/breadcrumbs |
vars.headerControlTextWeight |
Heading font weight |
Weight rules:
- font-weight: 500 β medium emphasis (card titles, labels)
- font-weight: 600 β strong emphasis (section headings, breadcrumb current)
- Accordion headers: text-transform: uppercase; letter-spacing: 0.06em;
Spacing
Standard values: 4, 6, 8, 10, 12, 16, 20, 24px
- Icon size: 28Γ28px (inline), 36Γ36px (detail headers)
- Card padding: 12px (bordered cards), 14px 16px (flat list rows)
- Section padding: 20px
- Gap between icon and text: 10px
- Gap between buttons: 8px
Icons
icon("Mail") // email action
icon("Code") // webhook action
icon("Dropdown") // accordion arrow, expand chevron
icon("Filter") // filter
icon("Warning") // alerts
icon("Remove") // delete
icon("FieldColumn") // column picker
Set color via CSS variable β never use color: on icons:
const cssIconWrap = styled("div", `
--icon-color: ${theme.controlPrimaryBg};
`);
Icon background container (standard):
const cssActionIcon = styled("div", `
width: 28px;
height: 28px;
border-radius: 6px;
background: ${theme.selectionOpaqueBg};
display: flex;
align-items: center;
justify-content: center;
--icon-color: ${theme.controlPrimaryBg};
flex-shrink: 0;
`);
Component Patterns
Bordered Card (interactive, e.g. Actions)
const cssCard = styled("div", `
border: 1px solid ${theme.inputBorder};
border-radius: 8px;
padding: 12px;
margin-bottom: 8px;
cursor: pointer;
&:hover {
border-color: ${theme.controlSecondaryFg};
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
}
`);
Flat List Row (Event Log / dense lists)
const cssRow = styled("div", `
display: flex;
align-items: center;
gap: 10px;
padding: 14px 16px;
border-bottom: 1px solid ${theme.inputBorder};
cursor: pointer;
&:hover {
border-color: ${theme.controlPrimaryBg}; // green divider on hover
}
`);
Hover rule: Bordered cards β box-shadow + border-color. Flat rows β change border-color (green). Never use background fill as hover for rows.
Expandable Row (inline detail)
// Card gets -expanded modifier, detail panel follows immediately
cssRow.cls("-expanded"), // removes bottom border
// detail panel:
const cssDetail = styled("div", `
padding: 12px 16px;
border-bottom: 1px solid ${theme.inputBorder};
background: ${theme.pageBg};
`);
Accordion Header
const cssAccordionHeader = styled("div", `
display: flex;
align-items: center;
gap: 8px;
padding: 10px 16px;
font-size: ${vars.mediumFontSize};
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: ${theme.lightText};
background: ${theme.pageBg};
border-top: 1px solid ${theme.inputBorder};
border-bottom: 1px solid ${theme.inputBorder};
margin-top: -1px;
cursor: pointer;
&:hover { color: ${theme.text}; }
`);
// Arrow: rotate(-90deg) closed, rotate(0deg) open
Badge / Pill
const cssBadge = styled("span", `
font-size: ${vars.xsmallFontSize};
font-weight: 500;
padding: 2px 8px;
border-radius: 999px;
white-space: nowrap;
&-enabled { background: ${theme.selectionOpaqueBg}; color: ${theme.controlPrimaryBg}; }
&-disabled { background: ${theme.pageBg}; color: ${theme.lightText}; }
&-error { background: rgba(208,2,27,0.08); color: ${theme.errorText}; }
`);
Dashed Add Button
const cssAddBtn = styled("div", `
border: 1.5px dashed ${theme.inputBorder};
border-radius: 8px;
padding: 10px;
text-align: center;
color: ${theme.lightText};
cursor: pointer;
font-size: ${vars.smallFontSize};
&:hover {
border-color: ${theme.controlPrimaryBg};
color: ${theme.controlPrimaryBg};
background: ${theme.selectionOpaqueBg};
}
`);
Radio / Option Cards
const cssOption = styled("label", `
display: flex;
align-items: flex-start;
gap: 10px;
padding: 10px 12px;
border: 1.5px solid ${theme.inputBorder};
border-radius: 8px;
cursor: pointer;
margin-bottom: 6px;
&-selected {
border-color: ${theme.controlPrimaryBg};
background: ${theme.selectionOpaqueBg};
}
`);
// Radio accent: style="accent-color: #16b378"
Two-Line Card Row Layout
Standard layout for action/log rows (icon + title/sub + right meta):
dom("div", { style: "display: flex; align-items: center; gap: 10px;" },
cssIcon(icon("Mail")),
dom("div", { style: "flex: 1; min-width: 0;" },
dom("div", { style: "font-weight: 500; font-size: 12px;" }, title),
dom("div", { style: `font-size: ${vars.smallFontSize}; color: ${theme.lightText};` }, sub),
),
cssRightSlot(/* badge, time, arrow */),
)
Text overflow for sub-line:
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
Common Mistakes
| Mistake | Fix |
|---|---|
Hardcoding #16b378 for green |
Use theme.controlPrimaryBg |
color: on icon elements |
Use --icon-color: CSS variable |
| Background fill on hover for flat rows | Use border-color change instead |
border-radius: 4px for cards |
Cards use 8px; only small elements use 4px |
Forgetting flex-shrink: 0 on icons |
Always add to prevent icon squish |
Using margin-top spacing in lists |
Use margin-bottom on each item or gap on parent |
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.