trendmicro-frontend/tonic-ui/tonic-ui-sx

Claude Code and agent skill by trendmicro-frontend/tonic-ui

Trust tier 2
  • community
LLM review pending
Source check pending

Checking that the SKILL.md file is still in the repository.

Choose sx, __sx, and composeSx when authoring or reviewing Tonic UI Box-based components, including wrappers and headless-library prop getters. Use for base styles, style precedence, consumer overrides that do not apply, or incoming __sx overrides that are dropped.

Install

npx vskill@latest install trendmicro-frontend/tonic-ui/tonic-ui-sx

SKILL.md

Tonic UI: sx vs __sx

All docs/ and packages/ paths are relative to the repository root. Component paths such as button/Button.js are relative to packages/react/src/. Read composition and verification when implementing a wrapper, transition, multi-feature prop getter, or regression check. Its worked examples and verification cases supplement the core rules below.

When to Use

Use this skill for component base styles, useXxxStyle() hooks, Box style routing, incoming __sx folds, wrappers overriding children, and headless-library prop getters such as react-table's getRowProps() / getHeaderProps(). It applies to any package rendering Tonic UI Box-based components, including external data-grid integrations. Also use it to investigate dropped wrapper styles or consumer overrides that lose.

Current Integration Status

Full __sx integration is not complete in this Tonic UI checkout. The helper, Box channel, and useSlot composition exist, and some components use them; the examples below describe the target styling convention, not proof that every component already follows it. Inspect the affected implementation before relying on it, and do not expand a task into full styling integration without a user request.

The one rule that governs everything: precedence-by-origin

A component's base styling always loses to a consumer override, regardless of which CSS property each touches. A wrapper's child-override beats the child's base but still loses to the end consumer. To make that true structurally (not by convention), Tonic UI routes who wrote the style to a fixed channel — and the channel decides precedence.

So the question is never "what does this style do?" but "who is writing it?"

  • You are the component author, styling your own `Box` → __sx (lowest tier).
  • You are a wrapper, overriding a child component you render → also __sx (folded into the child's incoming __sx).
  • You are the consumer (app developer) → sx (highest tier), or style props / pseudo props.

A component never writes its own styling to `sx`. sx is the consumer's escape hatch.

"Writing it" isn't limited to a component's own render body. A higher-order component wrapping a child, or a headless-library plugin contributing props via a getter function (e.g. react-table's getRowProps()/getHeaderProps(), composed across features by a shallow merge), is just as much "the component author" for this purpose as a render body is — the same hand-built-style-object, hand-merged-via-ensureArray-or-object-spread failure shows up there too, just one layer removed from JSX.

The four channels of Box

Box concatenates four ordered channels into one Emotion class. For declarations of the same property at the same selector specificity, a later channel wins.

ChannelWho writes itTierNotes
`__sx` (base sx)Library / component internals0 — lowestA component's own base styling. Full sx-object format. Internal: not in public BoxProps, never forwarded to the DOM.
Style props (px, bg, color, …)Consumer1Flat layout/appearance props, resolved by the system transform.
Pseudo props (_hover, _active, _focusVisible, …)Consumer3Each a single prop carrying a style object.
`sx`Consumer4 — highestThe consumer's arbitrary-style escape hatch.

__sx and sx accept the identical authoring format: flat declarations, nested selectors ('& svg', '&:hover'), pseudo shortcuts (_hover), theme => … functions, design tokens, responsive arrays. They differ only in tier (who they belong to), not in what they can express.

Why __sx exists (the problem it solves)

Before __sx, base styling was authored as style props (tier 1) + pseudo props (tier 3). That made precedence a function of which kind of prop a value is — not who wrote it. Two failures followed:

  • A component that needed nested selectors or pseudo-elements (which flat style props

can't express) had to author base styling via sx — but then the component's sx outranked the consumer's style props and sx (single shared slot), so the consumer could no longer override. Backwards.

  • Wrappers overriding a child had to squat on the consumer's sx channel and hand-merge the

consumer's sx back on top (sx={[ownSx, ...ensureArray(sx)]}) — done inconsistently across the codebase, silently dropping overrides when an author forgot the spread.

__sx is a dedicated lowest-priority channel that reuses the sx transform. Base styling goes there; consumer style props and sx sit above it and override it. Precedence-by-kind becomes precedence-by-origin, with no per-author merge bets.

How to author base styling: the uniform fold

Every base-styling component follows the same call site. You cannot predict which components a future wrapper will inject __sx into, so apply this uniformly — don't pick and choose:

js
import { composeSx } from '@tonic-ui/utils/internal';
const MyComponent = forwardRef((inProps, ref) => {
const {
// ...component props...
__sx: __sxProp, // destructure incoming __sx OUT of rest
...rest
} = useDefaultProps({ props: inProps, name: 'MyComponent' });
const styleProps = useMyComponentStyle(/* state */); // the COMPLETE base: flat + pseudo + nested
return (
<Box
ref={ref}
{...rest}
__sx={composeSx(styleProps, __sxProp)} // own base first, incoming __sx folded last
/>
);
});

Why each part matters:

  • `__sx` is destructured out of `rest` so it is not applied twice (once via the spread,

once via the explicit prop).

  • `composeSx(styleProps, __sxProp)` is placed *after* `{...rest}` so the explicit __sx

wins the spread, and the incoming __sx (already folded) lands after the component's own base → an injected override beats the component's base, while the consumer's sx (tier 4) still beats everything.

  • `useXxxStyle()` returns the component's complete base — flat layout, pseudo rules, and

nested selectors in one value (an object, or an array when conditional/order-sensitive parts must compose). There is no separate get*Sx function; it was folded into useXxxStyle.

Destructure `__sx: __sxProp` (and `...rest`) directly from `useDefaultProps` — in one line. Do not capture the whole bag as const props = useDefaultProps(...). You need two things the opaque bag can't give you cleanly: the incoming __sx pulled out (so it isn't applied twice) and a ...rest that no longer contains it (so the explicit __sx={composeSx(...)} after the spread wins). The one-line destructure is the canonical form:

js
// ✅ canonical — destructure inline
const { __sx: __sxProp, ...rest } = useDefaultProps({ props: inProps, name: 'X' });
const styleProps = useXxxStyle(...);
return <Child {...rest} __sx={composeSx(styleProps, __sxProp)} />;
// ❌ opaque bag — can't fold cleanly; `{...props}` carries an unfolded `__sx`,
// and adding `__sx={...}` alongside it double-applies or collides
const props = useDefaultProps({ props: inProps, name: 'X' });
return <Child {...props} __sx={composeSx(styleProps, props.__sx)} />;
// ❌ two-step — works, but re-destructuring from a captured bag is noise; inline it
const props = useDefaultProps({ props: inProps, name: 'X' });
const { __sx: __sxProp, ...rest } = props;

composeSx: array composition, never object merge

js
const composeSx = (...values) => values.flatMap((value) => ensureArray(value));
// import { composeSx } from '@tonic-ui/utils/internal'; (internal — not the public barrel)

composeSx is variadic — pass any number of sx-values (composeSx(base, a, b)); arrays are flattened and undefined is skipped. It returns an array for the __sx/sx prop. Do not spread it as props.

If you author the element through `useSlot`, you don't fold `__sx` yourself. useSlot composes both ref (via useMergeRefs) and __sx (via composeSx) across props and slotProps internally — put the base in props.__sx, pass the consumer's slot props as slotProps, and the base stays below the slot's __sx. Do not strip __sx out of the slot props or hand-merge it; that's the hook's job (mirrors how it merges ref):

js
const [Slot, slotProps] = useSlot({
props: { ref: combinedRef, __sx: baseStyle }, // base — useSlot keeps it below slotProps.__sx
slot: slots.x ?? Default,
slotProps: resolvedSlotProps, // consumer ref + __sx merged by useSlot
});

This is the single most important pitfall. Combining two sx-objects must use array composition ([a, b]), not object merge ({...a, ...b}):

  • Array → both objects are emitted; source order resolves conflicts per declaration.

An incoming &:hover partially overrides the base &:hover (changes color, keeps the base background).

  • Object merge → a nested key like &:hover from b replaces a's &:hover

entirely. The base hover styling is discarded. This is the wrong behavior and a common bug.

When there is no incoming value to fold (e.g. a sub-element whose style has no consumer __sx slot), pass the style object directly — composeSx adds nothing:

js
// Folding an incoming __sx → use composeSx:
<Box {...rest} __sx={composeSx(styleProps, __sxProp)} />
// No incoming __sx → pass directly:
return { ref, onMouseDown, __sx: trackStyleProps };

Transition and Prop-Getter Invariants

  • Animation state derived from an enum and static configuration belongs in __sx.

Fold incoming __sx last in both Box and function-child render paths.

  • Function children must spread the handoff onto a Box-based element. Keep the

caller's style separate and explicitly destructured as an animation input.

  • DOM-measured values belong in inline style; conditionally include measured keys

so an undefined measurement does not erase the caller's value.

  • Each prop-getter composition layer must fold multiple contributors' __sx via

composeSx, not shallow object spread.

  • Extract base styling into styles.js / use<Component>Style(), including static

values. Read the linked reference for the worked examples.

Consumer usage — sx

App developers override component styling with sx (highest tier), or with style props / pseudo props. They never touch __sx (it's internal and off the public type):

jsx
<Button sx={{ bg: 'blue:50', _hover: { bg: 'blue:60' } }}>Save</Button>
<Button bg="blue:50">Save</Button> {/* style prop, tier 1 — also beats base __sx */}

Both remain fully functional, coequal tiers — this doesn't change. Going forward, sx is the preferred authoring convention for new/migrated consumer JSX, including on the layout primitives (Box/Flex/Grid/Stack/StackItem/Space). The style-props-to-sx codemod (packages/codemod/src/style-props-to-sx) mechanically migrates existing flat-prop usage to sx.

The specificity boundary (read before promising "consumers can override anything")

Channel order resolves ties only at equal selector specificity. Specificity still applies:

  • A flat base declaration in __sx (e.g. { color }) is overridable by a consumer flat

style prop or sx.

  • A nested/pseudo base rule in __sx (e.g. { '&:hover': { color } }, { '& svg': {…} })

has higher specificity than a consumer's flat style prop, so a flat style prop will not override it — only a consumer rule of equal-or-higher specificity (a matching sx / _hover) will. This is inherent to CSS, not a quirk of __sx. Don't tell a consumer a flat color prop will override a base &:hover color.

Naming conventions

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewNot runA model review is scheduled for the next rescan.

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
133
Repository forks
39
7-day trend score
0

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/trendmicro-frontend/tonic-ui/tonic-ui-sx/badge)](https://verified-skill.com/skills/trendmicro-frontend/tonic-ui/tonic-ui-sx)

Something wrong with this skill or its page? Tell us.