Agent Skills: StyleX Styling

StyleX styling system with project-specific design tokens, composable primitives, and custom css prop. MUST consult this skill before writing or modifying ANY styles in this project — the codebase uses custom design tokens, flex primitives, motion presets, and a css prop that differs from standard StyleX. Trigger whenever the user asks to create components, modify visual appearance, fix spacing/layout, add hover/focus effects, animations, responsive behavior, or anything involving CSS, styling, design tokens, breakpoints, or the css prop.

UncategorizedID: qingqishi/shiqingqi.com/styling

Install this agent skill to your local

pnpm dlx add-skill https://github.com/QingqiShi/shiqingqi.com/tree/HEAD/.claude/skills/styling

Skill Files

Browse the full folder contents for styling.

Download Skill

Loading file tree…

.claude/skills/styling/SKILL.md

Skill Metadata

Name
styling
Description
StyleX styling system with project-specific design tokens, composable primitives, and custom css prop. MUST consult this skill before writing or modifying ANY styles in this project — the codebase uses custom design tokens, flex primitives, motion presets, and a css prop that differs from standard StyleX. Trigger whenever the user asks to create components, modify visual appearance, fix spacing/layout, add hover/focus effects, animations, responsive behavior, or anything involving CSS, styling, design tokens, breakpoints, or the css prop.

StyleX Styling

This project uses StyleX for all styling. The system has three layers: design tokens for values, design primitives for multi-property patterns, and stylex.create for component-specific styles. All styles are applied via a custom css prop.

The import paths below are the @tuja/ui package exports that apps/web uses. Inside packages/ui, import the same files by relative path, such as ../../tokens.stylex.ts from a component.

Quick Decision Guide

| Need | Use | Example | | ------------------------------------------------------ | ------------------------------ | ----------------------------------------------- | | Flex layout, fills, truncation, resets, transitions | Design primitives | css={flex.row} | | Rounded corners | Design primitives (corner.*) | css={corner.radius_3} | | Override a primitive's default | Layout modifier | css={[flex.row, align.end]} | | Single-property styling (color, spacing, font, border) | stylex.create + tokens | color: color.fg | | Responsive behavior | stylex.create + breakpoints | { default: "none", [breakpoints.md]: "flex" } | | Pseudo-selectors (hover, focus) | stylex.create | { default: val, ":hover": hoverVal } |

The css Prop

Use css={styles.foo} instead of {...stylex.props(styles.foo)}. This is StyleX's official JSX shorthand (sx), configured under the name css via the sxPropName Babel option.

// Single style
<div css={styles.card}>

// Composed — array of styles, primitives, and conditionals
<div css={[flex.row, styles.header, isActive && styles.active]}>

The transform only compiles css on lowercase host elements (div, svg, …). On a component, css is a real runtime prop carrying raw StyleX styles:

  • A component that should take styles declares css?: StyleProp (from @tuja/ui/types, or src/types.ts by relative path inside packages/ui) and composes it last into its root element's css array: css={[styles.base, css]}. Every @tuja/ui component works this way — css is the only styling entry; components do not accept className or style.
  • NEVER pass css to a third-party component (next/link, next/image, Phosphor icons) — it doesn't know the prop. Spread compiled props instead: <Link {...stylex.props(styles.cta)}>.
  • NEVER put an explicit className=/style= attribute on the same host element as css= — the compiled spread and the attributes clobber each other, and merging is never needed:
    • A runtime-computed value belongs in a dynamic style function, not a style attribute: stylex.create({ swatch: (bg: string) => ({ backgroundColor: bg }) }), applied as css={[styles.tone, styles.swatch(hex)]}. Custom properties work too: (x: string) => ({ "--nudge-x": x }).
    • A literal class required by a third-party stylesheet (the repo has exactly one: LyteNyte's ln-grid in media-table.tsx) is concatenated inline: const sx = stylex.props(...); <div {...sx} className={${sx.className ?? ""} ln-grid}>.

Design Tokens

Import from @tuja/ui/tokens.stylex. All tokens are theme-aware. For the full catalog of every token and its values, read references/tokens.md.

Categories: color, space, controlSize, font, border, shadow, layer, opacity, ratio, plus the constants and layout consts.

import { color, space, border, font } from "@tuja/ui/tokens.stylex";

const styles = stylex.create({
  card: {
    padding: space._4,
    borderWidth: border.size_1,
    backgroundColor: color.bgSurfaceRaised,
    fontSize: font.uiBody,
  },
});

Rounded corners are the one exception: don't reach for a bare border.radius_* here — use the corner primitive below instead, so the radius always ships paired with its corner shape.

Breakpoints

Import from @tuja/ui/breakpoints.stylex. Values: sm (320px), md (768px), lg (1080px), xl (2000px).

import { breakpoints } from "@tuja/ui/breakpoints.stylex";

const styles = stylex.create({
  grid: {
    display: { default: "none", [breakpoints.md]: "grid" },
    gridTemplateColumns: { default: "1fr", [breakpoints.lg]: "repeat(3, 1fr)" },
  },
});

Design Primitives

Composable multi-property styles in packages/ui/src/primitives/. Each primitive bundles 2+ CSS properties that encode a common pattern. For full API tables, read references/primitives.md.

Flex (@tuja/ui/primitives/flex.stylex)

The most commonly used primitives. Flex patterns set display: flex plus layout defaults:

  • flex.row — horizontal, vertically centered
  • flex.col — vertical stack
  • flex.center — centered both axes
  • flex.between — space-between with vertical centering
  • flex.wrap — wrapping row
  • flex.inlineCenter — inline-flex centered

Override defaults with modifiers: align.{start,center,end,baseline,stretch}, justify.{start,center,end,between}, grow.{_0,_1}, shrink.{_0,_1}.

import { flex, align, justify } from "@tuja/ui/primitives/flex.stylex";

<div css={flex.row}>                        {/* basic row */}
<div css={[flex.row, align.end]}>           {/* row, bottom-aligned */}
<header css={flex.between}>                 {/* toolbar pattern */}
<div css={[flex.col, justify.center]}>      {/* vertically centered column */}

Corner (@tuja/ui/primitives/corner.stylex)

Pairs each border.radius_* step with its corner shape in one declaration — squircle on corner.radius_1 … corner.radius_5, circular caps on corner.radius_round (clamped into a pill or a circle, a superellipse cap reads as neither). corner.squircle_round keeps the squircle shape at that same full-round radius, closing at half the cornerTokens.height dial — the shape Button and SegmentedControl use, each overriding the dial to their own control height. Rounded corners always go through this primitive; never write a bare borderRadius.

import { corner } from "@tuja/ui/primitives/corner.stylex";

<div css={corner.radius_3}>           {/* card corner */}
<span css={corner.radius_round}>      {/* pill / avatar */}
<button css={corner.squircle_round}>  {/* squircle pill, dialed via cornerTokens.height */}

If a radius genuinely can't go through the primitive — a vendor pseudo-element, a CSS-var-driven radius — pair cornerShape beside borderRadius in the same object literal instead ("squircle", or "round" at the full-round radius). The @tuja/require-corner-shape ESLint rule enforces this in packages/ui and apps/web.

There is no global corner-shape rule anywhere — every rounded corner carries its own shape through the primitive or a local cornerShape pairing.

Material (@tuja/ui/primitives/texture.stylex, @tuja/ui/primitives/wash.stylex)

Faint surface treatments — Texture, Wash, and Glass; the rules are on the texture and wash showcases (apps/web/src/design-system/sections/foundations/texture-showcase.tsx, wash-showcase.tsx). texture.dot draws one dot of 1px or less, repeated across a surface at one size — never nest a textured surface inside another, and never mix two sizes in one group. wash.toBottom/toTop/toRight/toLeft are a gradient of one tone fading to transparent — a Wash has no bright spot; a bright spot reads as a light source, and only Glass is lit.

Each dials its default through a token, overridden in a local stylex.create the way cornerTokens.height is:

import { texture, textureTokens } from "@tuja/ui/primitives/texture.stylex";
import { space } from "@tuja/ui/tokens.stylex";

const styles = stylex.create({ wide: { [textureTokens.pitch]: space._4 } });

<div css={[texture.dot, styles.wide]}>  {/* wider pitch for a wide surface */}

textureTokens.pitch/.ink default to space._1/color.fg at 20%; washTokens.tone defaults to color.bgNeutralSubtle.

Glass is the third Material but ships as a component style object, not a primitive: glassSurface from @tuja/ui/components/glass-surface.stylex, composed onto an element with position: relative plus a corner.* preset. glassTokens (fill, border, highlight, blur) is its dial, overridden the same way.

Other Primitives (see references/primitives.md)

  • Layout — position fills, scroll containers, truncation, image fit
  • Reset — buttonReset.base strips browser button chrome
  • Motion — transition/animation presets with reduced-motion handling
  • A11y — srOnly visually hides text while keeping it announced; focusRing/focusRingInset paint the keyboard focus ring

Best Practices

  1. Primitives for multi-property patterns — flex, fills, truncation, resets, transitions
  2. Tokens for single properties — fontSize: font.uiBody, gap: space._3
  3. Rounded corners via corner.*, never a bare borderRadius — pair cornerShape locally only where the primitive can't reach
  4. Always use the css prop — never {...stylex.props()}
  5. Conditional styles via arrays — css={[base, condition && conditional]}
  6. Mobile-first — use breakpoint overrides for larger screens
  7. Theme-aware colors — use color tokens that adapt to light/dark
  8. Logical properties — prefer paddingBlock/paddingInline over directional
  9. Pseudo-selectors as object keys — { default: val, ":hover": hoverVal }