CSS UI Enforcement
Hard UI conventions for Kevin-facing repos (wiki config, Dedalus, Sigil, Loop). Default surface: Tailwind utilities +
cn()only. Raw CSS is a last resort and, when required, lives inglobals.css(or the app’s single global entry - e.g. Next.jsapp/globals.css) - not in scattered files or CSS modules - unless the build truly cannot support that layout.
ENFORCE: Tailwind + cn() first; globals.css only for “nowhere else” rules
Ship UI as
className={cn(...)}everywhere: layout, spacing, color, typography, states, responsive, dark mode - use Tailwind utilities and design tokens exposed as utilities.cn()=clsx+tailwind-merge(shadcn-style~/lib/utils) so conditionals and conflicting classes resolve predictably.
Avoid
- Co-located
.css/.module.cssfor ordinary component styling - if it can be a utility, it must be. <style>tags, ad-hoc.scss, and one-off stylesheets per feature.
globals.css (or equivalent) - allowed only for what Tailwind cannot own cleanly
Examples: @layer base resets; @font-face; document-wide scrollbar defaults (if not using a Tailwind scrollbar plugin); @property registrations; keyframes that are genuinely global; :root / html / body plumbing for themes; third-party overrides that must target foreign selectors. If a rule could be a utility on the element, do not put it in globals.
Inline style={{}}
Same as before: do not mix random inline styles with Tailwind on the same node. Exceptions: true runtime geometry, third-party APIs, or a single CSS variable bridge - not “lazy CSS.” See § below.
ENFORCE: No style + className soup (React)
Mixing Tailwind on className with ad-hoc style={{ ... }} is an ugly pattern. Do not unless values cannot be classes or theme variables.
Do instead
- Static visuals → Tailwind on
classNameviacn(). - Dynamic but design-shaped → CSS variables on
:root/ theme layer or inline--tokenplus Tailwind arbitrary values - not parallel pixel objects.
Exceptions
- Runtime-only values (drag position, live chart layout, embed constraints).
- No “couldn’t find the class” exception.
See React. Source: User, 2026-05-11
ENFORCE: Scrollbars (thin + neutral)
Prefer a Tailwind scrollbar plugin / utilities if the project already has them. Otherwise define once in globals.css (document-wide), not per component:
/* globals.css - example; scope with @layer base if your stack requires */
html {
scrollbar-gutter: stable;
}
* {
scrollbar-width: thin;
scrollbar-color: gray transparent;
}
Add ::-webkit-scrollbar rules in the same global block when needed. Use scrollbar-gutter: stable when content shift appears as the scrollbar comes and goes; it reserves gutter space before overflow requires a visible scrollbar. Reach for stable both-edges only when a centered shell needs symmetrical gutters. Source: User, 2026-05-11; MDN scrollbar-gutter, checked 2026-07-06; X/@hugosaintemarie, 2026-07-01
ENFORCE: Hit area expansion (Tailwind-first)
Prefer Tailwind pseudo utilities on the interactive node - no separate CSS file:
className={cn(
"relative before:absolute before:inset-x-0 before:-inset-y-2.5 before:content-['']",
className,
)}
Tune before:-inset-y-* / before:inset-* for the real target size. If the same expansion repeats 20+ times, add a tiny utility in tailwind.config / @theme, or use Hit Area when a shadcn-installable Tailwind v4 utility fits the project. Do not scatter random .css modules. Pair with 44px guidance in Design and Animation. Source: User, 2026-05-11; Hit Area registry, 2026-07-04
ENFORCE: Agent context cost (lean-ctx)
For Claude Code and Cursor sessions in this brain/workspace, treat Lean Ctx as the default hook for reducing IDE/agent token load (claimed 60–95% reduction - verify per repo README). Install and wire per project before tuning prompts. Source: User, 2026-05-11; lean-ctx
Pattern: Raw CSS only when Tailwind cannot - keep it in globals.css
Advanced effects (@property, scroll-driven animation, global @keyframes) often need real CSS. Put them in globals.css (or one theme file imported from globals), not next to a single component, unless the framework forces otherwise.
@property for animatable gradients
Register in globals, consume angle/color via utilities + arbitrary values on the component:
/* globals.css */
@property --angle {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
Keep markup cn()-first. If the gradient + --angle interaction is too awkward as arbitrary utilities, add one shared class in globals.css (still not a CSS module). Source: User, 2026-05-11
Fluid type
Prefer Tailwind text-* + responsive/font-size tokens from your design system. If you need clamp() and it is not generated as utilities, add it once under @layer base on html or a token class in globals, not per file. Source: User, 2026-05-11
Scroll-driven counter() + cqh
If you use scroll timelines / counters, centralize the @keyframes / timeline plumbing in globals; the visible component still uses cn() for layout. Source: User, 2026-05-11
Jhey's reviewed demo is the canonical allowed shape: animate a typed custom property from 0 to 100 on scroll, render the value with counter(), and translate the label with container query height units (1cqh) so progress text follows the viewport-sized composition. This is worth raw CSS because @property, counters, and scroll timelines are platform plumbing; the component chrome around it should still be tokenized utilities. Source: X/@jh3yy and local video review, 2026-07-03
Agent workflow
When an agent touches UI, run this loop:
- Read the local project styling docs, then this page.
- Identify the token source: Sigil
var(--s-*), Reticle--ret-*, Tailwind theme tokens, or project-specific CSS variables. - Implement with Tailwind utilities and
cn()first. - Use inline style only for runtime geometry, third-party APIs, or a single CSS-variable bridge.
- Render the page in a browser and inspect the actual pixels before claiming completion.
- Run the repo's fastest lint/type/style check.
- If the same class pattern repeats enough to become policy, extract a token, utility, component, or style-page update.
This order prevents the common agent failure mode: writing plausible class strings that never get seen in a viewport. CSS is not done until it has rendered.
Product interaction defaults
Kevin's product defaults apply to UI built under this style guide:
- Interactions should feel like they resolve in about 100ms.
- No product tours.
- Use skeleton states for loading.
- Use larger hit targets for buttons and inputs.
- Support copy/paste where users move structured text.
- Provide honest one-click cancel for operations that can cost time, money, or data.
- Reassure users before destructive or lossy actions.
- Use
Cmd+Kcommand palette patterns for broad navigation. - Keep tooltips minimal and functional.
- Prefer short, simple slugs and URLs.
- Preserve resumable state when the task has continuity.
These are product rules, not aesthetic preferences. They keep operational tools fast enough to use repeatedly.
Review checklist
Before marking frontend work done, check:
- No ordinary component styling lives in CSS modules or one-off stylesheets.
- No
style+classNamemixture unless the style is runtime-only or a CSS variable bridge. - No raw hex values when a token exists.
- No nested UI cards unless the inner card is a repeated item or modal/tool surface.
- Hit targets remain usable on mobile and dense desktop layouts.
- Text does not overflow buttons, tabs, cards, or navigation.
- Loading, empty, error, disabled, and cancellation states exist where users expect them.
- Browser screenshot or Playwright proof exists for meaningful visual changes.
If the page needs custom CSS, leave a short comment at the global rule explaining why Tailwind cannot own it. No comment means the next agent should try to delete it.
Reference lattice
| Concern | Page / tool |
|---|---|
| AWS locally (lightweight) | Floci |
| Design tool discovery | Designtools.fyi, Shadcn Registry Directory (Top Blocks) |
| Native shell + web UI | Zero Native |
| Repeatable web automation skills | Autobrowse (Browserbase Skills) |
| React codebase hygiene | Security and Review Skills |
| Cursor multi-agent orchestration | Cursor Orchestrate Skill |
Timeline
- 2026-05-11 | ENFORCE: Tailwind +
cn()first;globals.cssonly for CSS Tailwind cannot own; Tailwindbefore:for hit expansion; no co-located CSS for ordinary UI; nostyle+class soup; scrollbars lean global or plugin;lean-ctx; tool cross-links. Source: User, 2026-05-11 - 2026-07-06 | Added
scrollbar-gutter: stableto the global scrollbar rule so disappearing/reappearing scrollbars do not shift app layouts; useboth-edgesonly for centered layouts that need symmetry. Source: MDNscrollbar-gutter; X/@hugosaintemarie, 2026-07-01 - 2026-06-30 | Deep-reviewed the Aceternity-linked
@propertyartifact: the visible before/after confirms typed custom properties are the correct route for animatable gradient angles/stops, while implementation still belongs in global CSS or a shared utility rather than a component CSS module. Source: X/@mannupaaji, 2026-05-10 - 2026-07-03 | Deep-reviewed Jhey's scroll-driven custom-property/counter demo and recorded it as an allowed raw-CSS escape hatch: global scroll/timeline/counter plumbing, utility-first component layout. Source: X/@jh3yy, 2025-01-18