Install
openclaw skills install @ivangdavila/cssWrites and debugs CSS: flexbox and grid layout, z-index and stacking, responsive breakpoints, theming, and render performance. Use when a layout breaks or overflows, z-index does nothing, the page scrolls sideways, position: sticky won't stick, text won't truncate, styles lose the cascade or need !important, content jumps while loading (CLS), transitions stutter, fonts flash, a modal or dropdown sits behind other content, form controls need restyling, a table needs sticky headers, printing comes out wrong, or a style works in Chrome but not Safari. Also for starting a stylesheet from scratch (reset and base layer), centering, container queries, fluid type with clamp(), design tokens and dark mode, RTL and logical properties, and modernizing old stylesheets with :has(), @layer, and nesting. Not for visual design taste — palettes, spacing scales, typeface choice.
openclaw skills install @ivangdavila/cssUser preferences and memory live in ~/Clawic/data/css/ (see setup.md on first use, memory-template.md for the file format). If you have data at an old location (~/css/ or ~/clawic/css/), move it to ~/Clawic/data/css/.
User-dependent variables. Defaults apply until the user states a preference; store them in ~/Clawic/data/css/config.yaml.
| Variable | Type | Default | Effect |
|---|---|---|---|
| authoring_mode | plain-css | sass | tailwind | css-in-js | plain-css | Syntax of every emitted example, and which advice in architecture.md applies (layers and nesting vs utility ordering vs zero-runtime extraction) |
| browser_support | evergreen | widely-available | legacy | evergreen | Gates which features ship bare vs behind @supports: legacy also adds vendor-prefixed fallbacks and blocks the Chromium-first features flagged in compatibility.md |
| naming_convention | bem | utility | css-modules | none | none | Class names in examples and the specificity ceiling enforced in reviews (architecture.md — Keeping It Clean) |
| rem_base | number px (10-16) | 16 | Every px→rem conversion, including the fluid-type derivation in responsive.md (rem term = px ÷ rem_base) |
| a11y_target | aa | aaa | aa | Which row of Accessibility Floor gates output: AA = 4.5:1 body / 24×24 targets, AAA = 7:1 body / 44×44 targets |
| explanation_depth | mechanism | fix-only | mechanism | How much of the why ships with each answer: mechanism names the cause (stacking context, flex sizing) before the declaration; fix-only emits the declaration plus one line |
| output_shape | diff | full-sheet | diff | Emitted code: diff = only the changed declarations in context; full-sheet = the complete rewritten stylesheet or component block |
Preference areas to record as the user reveals them:
explanation_depth and output_shape: comment density in emitted CSS, longhand vs shorthand, whether fallbacks and @supports branches are shown or assumed, how much of the trade-off to state before choosing!important in third-party overrides, how loudly to flag accessibility regressions@import), inherited legacy stylesheets that must keep workingheight: 100%, broken position: sticky, mystery horizontal scroll:has(), @layer, @scope, container queries, scroll snap, anchor positioning)| Situation | Play |
|---|---|
| z-index ignored despite a huge value | Stacking Contexts below — find the context root; never just bump the number |
| Flex item overflows / text won't truncate | min-width: 0 on the flex child (default min-width is min-content) |
| The rule is written but nothing changes | debugging.md — symptom→cause chains, starting with "is it even matching?" |
| Breaks with real content, sticky dead, footer floats, margin leaks | layout.md |
| Component must adapt to its container; fluid type; mobile viewport bugs | responsive.md |
Specificity fight, @layer, :has(), custom-property gotchas | selectors.md |
| Jank, layout shift, slow paint, font flash | performance.md |
| Transition never fires, enter/exit animation, view transitions, reduced motion | animations.md |
| Line-height inheritance, measure, variable fonts, hyphenation, tabular numbers | typography.md |
oklch vs hsl, color-mix(), gradient banding, wide gamut | color.md |
| Dark mode, design tokens, multi-brand, theme flash on load | theming.md |
| Inputs, selects, checkboxes, validation states, autofill styling | forms.md |
| Modal behind the header, dropdown clipped, tooltip placement | overlays.md |
| Scroll snap, custom scrollbars, anchor link lands under the sticky header | scrolling.md |
| Data table: sticky header, responsive behavior, column sizing | tables.md |
Shadows, filters, masks, clip-path, blend modes, 3D transforms | effects.md |
| Starting a stylesheet: what goes in the reset and base layers, in what order | reset.md |
| Sheet organization, layer strategy, nesting, Sass migration, shadow DOM | architecture.md |
| Works in Chrome, breaks in Safari or Firefox; HTML email | compatibility.md |
| Print or PDF output wrong | print.md |
| RTL mirroring, CJK line breaking, logical properties, text expansion | internationalization.md |
| Anything else CSS | Core Rules below, then debugging.md to name the mechanism |
transform and opacity — the only common properties that skip layout and paint. Frame budget = 1000ms / 60fps ≈ 16.7ms for style, paint, and your JS combined; one layout-triggering animation spends it alone.display: grid; place-content: center. Escape hatch: position: absolute; inset: 0; margin: auto when the child must overlay (needs a resolvable size, e.g. width: fit-content).font-size: clamp(1rem, 0.77rem + 0.91vw, 1.5rem) — the rem term is what keeps browser zoom and user font-size working; pure-vw text fails WCAG 1.4.4 (resize to 200%). Derivation of the numbers: responsive.md.min(), clamp(), fit-content, auto-fit grids), media queries second, container queries when one component lives at different widths.!important in component code is a debt marker. Order wars belong in @layer — unlayered author styles beat all layered ones regardless of specificity (selectors.md).<dialog>.showModal() and popover escape every stacking context and every overflow: hidden ancestor; a z-index arms race means the wrong mechanism is in use (overlays.md).margin, no width, no position on the component root — the parent layout owns placement. Components that set their own outer geometry break on the second reuse and get "fixed" with !important.The single most common CSS debugging failure: raising z-index on an element trapped inside a context.
opacity < 1, transform, filter, backdrop-filter, will-change, contain: layout or paint, position: fixed/sticky, isolation: isolate.z-index: 9999 never escapes its parent's z-index: 1.transform or opacity from an animation).isolation: isolate creates a context with zero visual side effects — use it to cap a component's internal z-index so it can't leak out.transform, filter, and will-change also make the element the containing block for position: fixed descendants — the fixed element behaves as absolute with no warning. Same walk-up diagnosis.<dialog>, popover) ignore all of the above: they paint above the page and above each other in open order (rule 7).flex: 1 = 1 1 0%: ALL space divided equally. flex: auto = 1 1 auto: only leftover space divided, so larger content keeps a larger track. Choose per intent; equal columns need basis 0.min-width: min-content — the root cause of both overflow and un-truncatable text. Release with min-width: 0 (or overflow: hidden). Column direction: same story with min-height.1fr means minmax(auto, 1fr): the track refuses to shrink below its content. grid-template-columns: 1fr 1fr is NOT 50/50 with unequal content — write minmax(0, 1fr) for true halves.auto-fit collapses empty tracks (remaining cards stretch); auto-fill keeps them (cards hold max width). Card grid default: repeat(auto-fit, minmax(min(250px, 100%), 1fr)) — the inner min() prevents overflow on viewports under 250px.gap never collapses; margins collapse (vertical, block layout only, including parent-child bleed-through). Prefer gap and treat margin collapse as legacy behavior to route around (layout.md).margin: auto on a flex/grid child absorbs free space: margin-inline-start: auto on the last nav item is the entire "push right" pattern.Compatibility floor: everything here is in all three engines unless marked; version-sensitive items are dated in compatibility.md.
:has() — parent and previous-sibling selection; kills a whole class of state-mirroring JS (selectors.md for patterns and cost).@starting-style + transition-behavior: allow-discrete — transition from display: none; replaces enter-animation JS (all engines since mid-2024).light-dark() + color-scheme — one declaration per token instead of a duplicated dark block (all engines since 2024; theming.md).text-wrap: balance on headings — engines skip long blocks (Chromium caps at 6 lines), so it is safe to apply to all headings.scrollbar-gutter: stable on scroll containers — reserves the gutter, no shift when the scrollbar appears.overscroll-behavior: contain on modals and drawers — stops scroll chaining into the page.scroll-snap-type + scroll-snap-align — carousels without JS (scrolling.md).aspect-ratio — reserve media space before load (layout-shift numbers: performance.md).accent-color — form controls on brand without rebuilding them (forms.md).@scope and native nesting — component boundaries without naming conventions; specificity traps in architecture.md.translate, rotate, scale) — compose in a fixed order and animate independently, no more one-property transform collisions.anchor-name, position-area) — tethered popovers without a positioning library; still needs a fallback, see overlays.md.Canonical home for these numbers — other files point here.
a11y_target: aaa.a11y_target: aaa.@media (prefers-reduced-motion: no-preference) rather than overriding after the fact.:focus-visible; never outline: none without a replacement in the same rule.@media (forced-colors: active): system colors replace yours — check borders and focus still exist there.@media (prefers-color-scheme: dark) plus color-scheme: light dark so form controls and scrollbars follow.display: none / visibility: hidden remove content from the accessibility tree; aria-hidden hides from assistive tech while staying visible; screen-reader-only text needs the clip pattern — position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap.Before shipping a stylesheet or component styles, verify:
:focus-visible style and meets the contrast and target-size rows above?prefers-reduced-motion: no-preference, and only transform/opacity in the frame loop?aspect-ratio or width/height attributes)?!important outside a third-party override, and no new selector above single-class specificity?internationalization.md is in scope?| Trap | Why it fails | Do instead |
|---|---|---|
| Bumping z-index to 9999 | Element is inside a stacking context; only the context root competes outside | Walk-up procedure (→ Stacking Contexts) |
| Animating height/top/left/margin | Layout runs every frame and blows the 16.7ms budget (Core Rule 2) | transform; for height-to-auto, the grid-rows trick (→ layout.md) |
overflow: hidden to kill a stray scrollbar | Hides the symptom and creates a scroll container: breaks sticky descendants, clips shadows and focus rings | Find the overflowing element first; when it cannot be removed, html { overflow-x: clip } — clip creates no scroll container, so sticky survives (→ layout.md) |
var(--x, fallback) as a safety net | A declared-but-invalid value skips the fallback ("invalid at computed-value time") | @property with initial-value (→ selectors.md) |
Global will-change or translateZ(0) "GPU hints" | Every layer holds GPU memory; hundreds of layers slow compositing | will-change only on elements actually animating, only while animating (→ performance.md) |
100vh full-screen sections | Mobile browser UI overlaps the bottom of the section | 100svh; dvh only when live resize is acceptable (→ responsive.md) |
!important to win a specificity fight | Escalation is one-way; the next override needs another !important | @layer ordering (→ selectors.md) |
:empty for empty states | Whitespace text nodes count as content in most engines | Control the markup, or a class set by the renderer |
transition: all | Animates properties added later — including layout ones — and turns theme swaps into visible sweeps | Enumerate the properties you mean (→ animations.md) |
:invalid for error styling | Matches an untouched empty required field on first paint: the form is red before typing | :user-invalid (→ forms.md) |
line-height: 150% | Percentages inherit the COMPUTED value, so a big heading inherits the body's pixel leading | Unitless line-height: 1.5 (→ typography.md) |
display: none for screen-reader-only text | Removes it from the accessibility tree — nobody hears it | The clip pattern (→ Accessibility Floor) |
text-align: left and margin-left in a themeable product | The first RTL locale mirrors everything except your CSS | Logical properties (→ internationalization.md) |
:has() or universal selectors on large, frequently-mutating DOMs (performance.md).@layer); internal dashboards can afford runtime styling.More Clawic skills, get them at https://clawic.com/skills/css (install if the user confirms):
html - semantic structure and document-level concerns the CSS hooks intofrontend - component architecture, frameworks, and build tooling around the stylesanimations - motion design and choreography beyond single-property transitionsaccessibility-audit - full WCAG review beyond the CSS floor heredesign-system - tokens, theming, and scaling styles across a productPart of Clawic, the verified skill library. Get this skill: https://clawic.com/skills/css.