> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wpos.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Styling

> The styling ladder: the tw Tailwind subset, the sx prop vocabulary, and the raw CSS escape hatch.

elementor-jsx styles compile to Elementor's atomic style envelopes, validated at
build time. Invalid values throw with the fix in the error message instead of
silently rendering wrong. There are three layers; always reach for them in this
order.

## The styling ladder

1. **`tw="..."`** for anything the Tailwind subset covers: layout, spacing, type
   scale, radii, shadows. Shortest and most reviewable, and models emit it
   natively.
2. **sx props** for what tw cannot express: partial sides, gradients, computed
   values, theme tokens. `pad={{t: 64}}`, `grad={[160, '#111', '#333']}`,
   `size={47}`.
3. **`raw="..."`** last, and only for genuinely non-atomic CSS: nested selectors
   (`& em {...}`), pseudo-classes, filters, absolute-position offsets.
4. Explicit sx props win over tw on conflict; do not set both for the same
   property.

## The tw subset

Desktop-first, mirroring sx: base = desktop, `max-lg:` = tablet, `max-md:` =
mobile. Spacing scale is n x 4px (`p-6` = 24px). Arbitrary values work:
`p-[96px]`, `w-[50%]`, `text-[15px]`, `bg-[#0A2230]`, `max-w-[960px]`.

```jsx theme={null}
<section tw="flex flex-col items-center gap-8 w-full px-6 py-24 max-md:py-16">
```

Unknown utilities **throw at compile time**, naming the token: a silently
dropped class is a visual bug only a screenshot catches. Three deliberate gaps
get targeted errors with the working recipe:

* **Palette names** (`bg-blue-500`): use a literal or a theme token.
* **`hover:` and other state prefixes**: state styling goes through
  `raw="&:hover {...}"`.
* **Mobile-first prefixes** (`md:`, `lg:`): the subset is desktop-first; use
  `max-lg:` and `max-md:`.

## sx props

The full key list is on the [API card](/ultra/api-card). The rules that matter:

### Sizes and units

`w h maxw minh gap size radius pad m`: a bare number means px, or pass a unit
string with `px`, `%`, `em`, `rem`, `vw`, `vh`, or `ch`. Units are honored
(`maxw="88vw"` really is 88vw). `w` and `h` also take `'hug'` and `'auto'`.

Anything else, including `calc()`, `clamp()`, keywords, and two-value gap,
**throws at build time**. Put those in `raw=`.

`pad` and `m` also take `[v, h]`, `[t, r, b, l]`, a partial `{t, r, b, l}`
object, and `'0 auto'` strings.

### Alignment

`align` and `justify` are validated against Elementor's enums at build time:

* `justify`: `center`, `start`, `end`, `flex-start`, `flex-end`,
  `space-between`, `space-around`, `space-evenly`, `stretch`. The shorthands
  `'between'`, `'around'`, `'evenly'` auto-map.
* `align`: the same minus the `space-*` values, plus `self-start` and
  `self-end`. There is **no baseline**; use `flex-end`.
* `dir` takes `'row'` or `'column'` (`'col'` throws).

### Color and background

* `color` and `bg` take hex values, **not** gradient strings.
* `grad` takes an array: `grad={[135, '#0ff', '#f0f']}` as angle, from, to. A
  CSS gradient string throws. Freeform gradients go in
  `raw="background-image: ...;"`.
* `bgImage` takes a URL or an attachment id; `bgOpts` takes
  `{color, size: 'cover', position: 'center center', repeat: 'no-repeat', attachment}`.

### Typography

`size weight font lh ls ta` cover the type system. Bare-number `lh` (up to 4)
and `ls` are read as **em**, so `ls={-1}` collapses a headline to unreadable.
Explicit units are honored: `lh="150%"`, `ls="2px"`.

### Responsive

`tablet={{...}}` and `mobile={{...}}` take the same sx keys as breakpoint
overrides:

```jsx theme={null}
<heading tag="h1" size={64} mobile={{ size: 40 }}>Headline</heading>
```

## raw: the escape hatch

`raw=""` takes CSS declarations (auto-terminated) and supports nested selectors
and pseudo-classes:

```jsx theme={null}
<text raw="& strong { color: #F59E0B; } &:hover { opacity: 0.8; }">
```

<Warning>
  Raw compiles to `custom_css`, which only renders through global classes (or
  the `--inline` build flag). Atomic tw/sx props always render. This is why the
  ladder exists, and why `exjsx lint` flags raw declarations that atomic props
  already cover (`raw-atomic-overlap`) and oversized raw blocks
  (`oversized-raw`).
</Warning>

One sharp edge: class-only `display: grid` loses to Elementor's atomic flex CSS
printed later in the body. Set `display` and grid props via sx (atomic), and
keep only the extras (auto-rows, dense, bleed) in raw.

## Semantic class names: cls and gcls

* `cls="name"` labels the element's deduped shared class, so the Elementor
  Class Manager shows `card` instead of `c-1x9fq2`. Give any pattern used three
  or more times a `cls` (lint: `unnamed-shared-class`).
* `gcls="name"` attaches an arbitrary extra class you style yourself (in raw or
  an html carrier).

## Next

<CardGroup cols={2}>
  <Card title="Components" icon="puzzle-piece" href="/ultra/components">
    What you can put these styles on.
  </Card>

  <Card title="API card" icon="book" href="/ultra/api-card">
    The complete sx key list on one page.
  </Card>
</CardGroup>
