PocketJS

Tailwind utilities

PocketJS styles come from a build-time Tailwind subset. There is no runtime Tailwind, no CSS, and no arbitrary-utility escape hatch: a fixed set of utilities is parsed at build time by compiler/tailwind.ts and baked into styles.bin, which the Rust core reads directly. This page is the exhaustive reference for exactly which utilities exist and what values they accept.

For how styling fits into the pipeline, see /docs/styling/ and /docs/build-pipeline/.

import { View, Text } from "@pocketjs/core/components";

function Card() {
  return (
    <View class="flex flex-col gap-2 p-4 bg-slate-800 rounded-lg">
      <Text class="text-lg font-bold text-slate-50">Hello PSP</Text>
    </View>
  );
}

How a class literal compiles

A candidate string is split on whitespace into tokens. Every token must parse as a supported utility for the string to become a style record.

  • If all tokens parse, the literal is compiled to a styleId and stored in styles.bin. The class attribute is resolved to that id natively.
  • If any token is not a supported utility, the whole literal is silently ignored — it is assumed to be ordinary text, not a class string. There is no partial application: one unknown token drops the entire literal.
  • Token order does not matter. Records are canonicalized and deduped, so "p-4 flex" and "flex p-4" share one styleId.

Two constructs are compile errors rather than silent drops — see Not supported below.

The spacing scale

Most sizing/offset utilities take a value on Tailwind's spacing scale, where the numeric step N maps to N * 4 pixels. Decimals are allowed.

Token part Pixels
0 0
1 4
2 8
3 12
4 16
6 24
8 32
12 48
2.5 10

Arbitrary pixel values are supported for every spacing-scale utility using bracket syntax — the value is taken as literal pixels (the px suffix is optional):

<View class="w-[123] h-[123px] p-[10] gap-[6px]" />

A second group of utilities (z, opacity, scale, scale-x, scale-y, rotate, duration, delay) take a plain number instead. Those do not accept bracket/arbitrary syntax — only bare digits.

Negative values are not supported. There are no -m-2, -translate-x-2, -rotate-45, etc. Any token beginning with - fails to parse.

Color palette

Colors come from the Tailwind v3 default palette. A color reference is {family}-{shade}, plus the three keywords white, black, transparent.

Families: slate, gray, zinc, red, orange, amber, yellow, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose.

Shades: 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950.

Colors are consumed through these prefixes:

Prefix Applies to Example
bg-{color} background fill bg-slate-800, bg-white
text-{color} text color text-emerald-400
border-{color} border color (also sets a 1px border) border-slate-600
from-{color} gradient start stop from-sky-500
to-{color} gradient end stop to-blue-700

An unrecognized family or shade (e.g. bg-slate-999, text-brand-500) does not parse, so the whole literal is ignored.

Flex

Utility Effect
flex display: flex
flex-row main axis = row
flex-col main axis = column
flex-wrap allow wrapping
flex-1 grow: 1, shrink: 1, basis: 0
grow flex-grow: 1
grow-0 flex-grow: 0
shrink-0 flex-shrink: 0
basis-N flex basis (spacing scale)
gap-N gap between children (spacing scale)
justify-start | -center | -end | -between | -around main-axis distribution
items-start | -center | -end | -stretch cross-axis alignment

Only the five justify-* and four items-* values above exist (no justify-evenly, no items-baseline).

Box & position

Utility Effect
w-N | w-full | w-[px] width (spacing, full, or arbitrary px)
h-N | h-full | h-[px] height
min-w-N, min-h-N, max-w-N, max-h-N min/max size (spacing or arbitrary px)
p-N, px-N, py-N, pt-N, pr-N, pb-N, pl-N padding
m-N, mx-N, my-N, mt-N, mr-N, mb-N, ml-N margin
absolute position: absolute
relative position: relative
inset-N, top-N, right-N, bottom-N, left-N position offsets (spacing or arbitrary px)
hidden display: none
overflow-hidden clip children (native scissor)
z-N z-index (plain integer)

w-full / h-full are the only percentage-style sizes. min-* / max-* do not accept -full. The padding/margin/inset families take the spacing scale or arbitrary px; p-N and m-N fan out to all four sides, px/py and mx/my to the two axes.

Visual

Utility Effect
bg-{color} background color
bg-gradient-to-t | -b | -l | -r gradient direction (top / bottom / left / right)
from-{color} gradient start color
to-{color} gradient end color
rounded 4px corner radius
rounded-sm 2px
rounded-md 6px
rounded-lg 8px
rounded-xl 12px
rounded-full pill/circle — build-time size required (see below)
opacity-N opacity, N/100 (0–100)
shadow small shadow
shadow-md medium shadow
shadow-lg large shadow
border 1px border (width only)
border-{color} border color and a 1px border

Gradients only run along the four cardinal directions (no diagonals). Set a direction with bg-gradient-to-* and the stops with from-* / to-*:

<View class="bg-gradient-to-b from-sky-500 to-blue-700 w-full h-16" />

Border width is fixed at 1px. border-2, border-4, etc. do not exist — only bare border and border-{color}.

Only three shadow steps exist: shadow, shadow-md, shadow-lg.

rounded-full

rounded-full bakes an exact radius at build time, so it needs the node's width and height to be build-time known in the same literal via w-N/h-N (or arbitrary px w-[px]/h-[px]). The radius becomes min(w, h) / 2.

{/* OK — concrete w/h, radius baked to 24px */}
<View class="w-12 h-12 rounded-full bg-rose-500" />

{/* Compile error — w-full is not a build-time pixel size */}
<View class="w-full h-12 rounded-full" />

Using rounded-full without a concrete w-N and h-N is a hard compile error, not a silent drop.

Text

Utility Effect
text-{color} text color
text-xs 12px
text-sm 14px
text-base 16px (default)
text-lg 18px
text-xl 20px
text-2xl 24px
text-4xl 36px
font-bold bold weight (baked bold atlas)
text-left | text-center | text-right horizontal alignment
leading-N line height (spacing scale or arbitrary px)
tracking-wide letter spacing = 0.025 × font-size

Font sizes are baked into atlases at build time, so only the seven sizes above exist: 12 / 14 / 16 / 18 / 20 / 24 / 36 px. There is no text-3xl, text-5xl, etc. Each size ships in two weights (regular and, when font-bold is used, bold). Text with no size/weight utility inherits the 16px regular default.

Only font-bold exists for weight (no font-normal / font-semibold), and only tracking-wide for tracking.

Transform

Transforms are animatable and do not trigger relayout — prefer them for motion.

Utility Effect
translate-x-N translate X (spacing scale or arbitrary px)
translate-y-N translate Y (spacing scale or arbitrary px)
scale-N uniform scale, N/100 (e.g. scale-105 → 1.05)
scale-x-N X scale, N/100
scale-y-N Y scale, N/100
rotate-N rotation in degrees (e.g. rotate-45)

scale-* and rotate-* take plain numbers only (no bracket syntax, no negatives).

Motion / transition

Adding any motion token to a literal attaches a transition block to that style. Transitions fire when the style is swapped (for example on focus: / active:) and interpolate the animatable properties in the mask.

Utility Effect
transition animate colors, opacity, and transforms
transition-all animate all animatable properties
transition-colors animate bg / text / border / gradient colors
transition-opacity animate opacity
transition-transform animate translate / scale / rotate
duration-N duration in ms (0–65535)
delay-N delay in ms (0–65535)
ease-linear | -in | -out | -in-out | -spring | -out-back easing curve

duration-N and delay-N are plain millisecond values (bare numbers). spring and out-back are PocketJS additions beyond the standard Tailwind easings.

Defaults. When a literal contains any motion token, unspecified fields fall back to: duration 150ms, delay 0ms, easing in-out. The property mask defaults to all animatable properties unless you used the bare transition shorthand (which is limited to colors + opacity + transforms). So duration-200 ease-out on its own still animates every animatable property.

<View class="bg-slate-700 transition-colors duration-200 focus:bg-slate-500" />

Variants

Two state variants are supported. They compile into separate blocks of the same style record and are switched natively — no JS runs on focus change.

Variant Applies when
focus: the node is the focused node
active: the node is pressed/active
<View class="bg-slate-700 border-slate-600
             focus:bg-slate-600 focus:border-blue-500
             active:scale-95 transition" />

Any utility can be prefixed. Motion tokens (transition*, duration, delay, ease) apply to the whole record and are only recognized on the base variant, not behind focus: / active:.

Not supported (loud errors)

These are rejected loudly at build/dev time, not silently dropped:

Construct Why
classList={{…}} dynamic class objects are not supported; the renderer raises a dev error
Template-interpolated class fragments (e.g. class={`p-${n}`}) classes must be static literals so they can be collected and baked at build time
hover: the PSP has no pointer — using hover: in an otherwise-valid literal is a hard compile error; use focus: / active:
rounded-full without build-time w-N/h-N the radius must be bakeable at build time

For dynamic styling, use one of:

  • Ternaries of complete literals — swap whole class strings, e.g. class={ok() ? "bg-green-600" : "bg-red-600"}.
  • style={{…}} objects — per-key dynamic props applied at runtime.
  • animate() — imperative property animation (see /docs/animation/).

See also /docs/styling/ for the styling model overview and /docs/components/ for the primitives that accept class.