Skip to content

CSS reference

PAM Native compiles CSS from src/app.css, @imported files and component <style> blocks at build time. Every declaration becomes a typed native property; nothing is parsed while rendering. Layout runs in the Rust engine (shared by Android and iOS), paint runs in the platform renderer.

Unsupported but valid CSS never disappears silently: the compiler fails with <file>:<line>: <message> pointing at the declaration (for example src/Screens/Chat.pam.php:212: CSS property float has no native layout/paint equivalent in …). Properties with no native meaning (see No-op properties) are accepted and ignored on purpose.

Legend: A Android renderer, i iOS renderer, L shared layout engine (both platforms). — means a compile-time diagnostic.

PAM follows React Native/Yoga defaults: flex-direction: column, align-items: stretch, flex-shrink: 0, align-content: flex-start.

Property Values Where
display flex, inline-flex, block, flow-root, grid, inline-grid, none (contents/inline/table → —) L
flex-direction row, row-reverse, column, column-reverse L
flex-wrap nowrap, wrap, wrap-reverse L
flex-flow <direction> <wrap> L
flex none, auto, initial, <grow>, <grow> <shrink>, <grow> <shrink> <basis>, <basis> L
flex-grow, flex-shrink <number> L
flex-basis <length>, <percentage>, auto, content, calc() L
order <integer> ≥ 0 L
justify-content flex-start/start/left, center, flex-end/end/right, space-between, space-around, space-evenly L
align-items, align-self flex-start, center, flex-end, stretch, baseline, auto (self) L
align-content flex-start, center, flex-end, stretch, space-between, space-around, space-evenly L
place-items, place-content, place-self shorthands of the above L
gap, row-gap, column-gap <length>; gap: <row> <column> L
margin: auto (any side) absorbs free space on the main axis, aligns on the cross axis L

flex-basis semantics: the flexible-length algorithm of CSS Flexbox §9.7 (grow, shrink weighted by shrink × basis, min/max freezing). A growing item without a basis starts from 0 (flex-grow: 1 behaves like flex: 1 1 0, as in React Native). Write flex-basis: auto (or flex: 1 1 auto) to start from the content/explicit size. flex: 0 keeps the content size because native layout has no CSS automatic minimum size.

display: grid, grid-template-columns: repeat(N, …) / track lists (1–64 tracks), grid-column: span N, gap/row-gap/column-gap. Grid placement by line numbers or areas → —.

Property Values Where
width, height, min-*, max-* <length>, <percentage>, auto, none (max), calc()/min()/max()/clamp() (min-content/max-content/fit-content → —) L
aspect-ratio <number>, <w> / <h>, auto L
padding, margin (+ -top/-right/-bottom/-left, -inline, -block, -inline-start/end, -block-start/end) 1–4 values, auto (margin) L
box-sizing border-box (always; content-box → —) L
position static/relative (offsets shift the box), absolute, fixed (relative to the viewport; sticky → —) L
top, right, bottom, left, inset, inset-inline/block <length>, <percentage>, auto L
z-index <integer>, auto A i
overflow, overflow-x/y visible, hidden, clip (auto/scroll → use <ScrollView>) A i
visibility visible, hidden, collapse A i
opacity <number>, <percentage> A i
Property Values Where
border, border-top/right/bottom/left width, style, color in any order; none A i
border-width (+ per side) 1–4 values, thin/medium/thick A i
border-color (+ per side) 1–4 values (per-side colors join with CSS miters) A i
border-style (+ per side) solid, dashed, dotted, none (one style per box) A i
border-radius (+ per corner, logical corners) 1–4 lengths, 50%+ (pill/circle); other % and elliptical / → — A i
box-shadow comma-separated list of [inset] x y [blur] [spread] [color], none A i
border-image, border-image-source <linear/radial gradient> [1], none — a gradient stroke over the border widths that follows border-radius (story rings); border-image-slice: 1, other border-image-* initial values only A i
elevation Android elevation (iOS: Material-like layer shadow) A i

Shadow details: blur follows CSS (Gaussian σ = blur / 2), spread grows the shape and its non-zero radii, shadows paint last-to-first under the box (outer) or inside the padding box above the background (inset). Outer shadows are drawn by the parent container from cached ALPHA_8 nine-slice masks (one small bitmap per radius/blur combination, shared by every view; large blurs are rendered downscaled), so lists of shadowed cards cost one bitmap draw per shadow and never re-blur on scroll.

Property Values Where
background-color any color, none, transparent A i
background comma-separated gradient layers plus an optional final color (linear-gradient(…), radial-gradient(…), #fff); no-repeat accepted; url()/image-set() → — (use <ImageBackground>) A i
background-image gradient layers, none A i
background-size/-position/-repeat/-clip/-origin/-attachment neutral values only (cover, 100% 100%, 0 0, no-repeat, border-box, scroll); gradients always cover the border box A i
filter blur(), brightness(), contrast(), saturate(), grayscale(), sepia(), invert(), opacity(), hue-rotate(), in any combination/order, none (drop-shadow()/url() → —) A (blur: API 31+), i (images only)
backdrop-filter, -webkit-backdrop-filter same functions as filter, none A (API 31+, containers), i (blur only)
mix-blend-mode, clip-path, mask normal / none only —

linear-gradient(), radial-gradient(), repeating-linear-gradient(), repeating-radial-gradient() and legacy -webkit-linear-gradient():

  • Direction: angles (deg, rad, grad, turn), to <side> and magic corners (to top right — perpendicular to the box diagonal, resolved with the painted size).
  • Radial: circle/ellipse, closest-side, closest-corner, farthest-side, farthest-corner (default) or explicit radii (40px, 40px 50%), at <position> with keywords, percentages and lengths (1–2 values).
  • Color stops: any CSS color, transparent, positions in % or lengths, double positions (red 10px 40px), auto positions distributed as in CSS, hard stops. Interpolation happens in premultiplied space like browsers, so transparent fades never darken. Color hints (red, 30%, blue) and conic-gradient() → —.
  • Layers paint in CSS order (first on top) over background-color, clipped anti-aliased to border-radius. Shaders are rebuilt only when the box size changes; parsed gradients are cached per wire string.

<LinearGradient> mirrors expo-linear-gradient for React Native ports:

<LinearGradient colors="rgba(0,0,0,.55), transparent" start="0.5, 0" end="0.5, 1" locations="0, 1" class="scrim"/>
<LinearGradient :colors="$ringColors" :start="$ringStart" :end="$ringEnd"/>

colors (list or comma string), start/end ({x, y} or [x, y] box fractions, default top-center → bottom-center) and locations (fractions) compile to the same native gradient; children render on top like <View>.

  • <Shimmer baseColor gradientColor duration enabled> — skeleton sweep matching Zé Chat’s native shimmer (highlight strip 1.25× the width, stops 0/.35/.5/.65/1, default gradientColor #FFFFFF59, 1200 ms). baseColor is the background; children render above the sweep; it clips to border-radius and pauses when detached, hidden or off screen (one shared Choreographer callback for every shimmer).
  • <Image blurRadius="28"> (and filter: blur() on images) blurs the decoded bitmap once, like React Native: every API level, opaque edges, no per-frame GPU work (σ = the radius in dp).
  • text-shadow: x y <blur> <color> maps the blur to Android’s setShadowLayer radius exactly like React Native textShadowRadius.
  • Modal/BottomSheet scrims: backdropColor="rgba(0,0,0,.28)".

filter color functions are composed at compile time into one 4×5 color matrix. On Android 12+ blur and the matrix run as a GPU RenderEffect (blur σ = the CSS length; edges fade like browsers); before API 31 the color matrix uses a hardware layer and blur() is a no-op with a one-time PamNative log warning.

backdrop-filter (Android 12+, on container nodes) re-records what is painted behind the element every frame it draws — ancestor backgrounds and earlier siblings along the ancestor chain, referencing their existing render nodes — applies the blur/color matrix on the GPU and clips it to border-radius. Limits: SurfaceView video is not captured (use the default texture-backed <Video>), siblings painted later in z-order are ignored, and before API 31 it is a no-op (keep a translucent background-color as the fallback). Cost: one GPU blur of the element’s area per frame while it is visible — prefer it for headers/sheets, not list rows.

iOS (1.9.0): gradients (linear, radial, repeating, premultiplied stops) are drawn with Core Graphics in a layer below the children and clipped to the radius; border-image gradient rings and per-side colors are drawn above the children; every outer and inset box-shadow is a Core Animation shadowPath layer (rasterized and cached by the render server; outer shadows of clipping views live in a sibling layer right below the view so overflow: hidden never cuts them). filter (blur and the color matrix) applies to images only — iOS has no public per-view filter API — and other views log a one-time debug diagnostic. backdrop-filter: blur() uses a UIVisualEffectView whose strength is scrubbed to the CSS radius; its color functions log a diagnostic.

Property Values Where
transform translate(), translateX/Y() (lengths or % of the own box), translate3d(x, y, 0), scale(), scaleX/Y(), rotate()/rotateZ() (deg, rad, grad, turn), non-skewing matrix(), none (skew/3D → —) A i
translate, scale, rotate individual transform properties A i
transform-origin keywords and percentages A i
transition (+ -property, -duration, -timing-function, -delay) native implicit animation on the UI thread with per-property durations, delays and easings (linear, ease*, cubic-bezier(), steps(), spring(mass stiffness damping)); see Gestures and animation A i
@keyframes + <Animated keyframes="…"> opacity/translate/scale/rotate keyframes run natively A i
animation* properties — (attach keyframes with <Animated>) —
Property Values Where
font [style] [variant] [weight] <size>[/<line-height>] <family> A i
font-family first family of the list; @font-face with packaged asset:// TTF/OTF A i
font-size lengths, rem, em (= rem for font-size), keywords xx-small…xxx-large A i
font-weight 100–900, normal, bold, bolder, lighter A i
font-style normal, italic, oblique A i
font-variant-numeric, font-variant(-caps/-ligatures), font-feature-settings tabular-nums, lining-nums, oldstyle-nums, slashed-zero, small-caps, ligatures, "tag" on/off/N A i
line-height <length>, unitless multiplier, %, em, normal A i
letter-spacing <length>, em, normal A i
text-align left/start, center, right/end, justify A (justify API 26+), i
text-transform none, uppercase, lowercase, capitalize A i
text-decoration, text-decoration-line none, underline, line-through, both (solid, text color) A i
text-shadow one shadow x y [blur] [color], none A i
text-overflow ellipsis, clip A i
-webkit-line-clamp, line-clamp <integer> A i
white-space, text-wrap nowrap/pre → one line; normal, pre-wrap, pre-line, balance, pretty A i
word-break, overflow-wrap, word-wrap normal, break-word, anywhere, break-all, keep-all A i
hyphens none, manual, auto A
direction ltr, rtl A i
color, caret-color, ::placeholder { color }, ::selection { color } colors A i
user-select none, text, all, auto A i
vertical-align, text-indent, writing-mode (non-default) — —

Text properties (color, font, line-height, letter-spacing, text-align, text-transform, font-feature-settings, text-shadow) inherit through containers, like CSS.

px, dp, pt (all equal to device-independent pixels), sp, rem (16), em (the element’s font size), %, vw, vh, vmin, vmax, dvh/svh/ lvh (and *vw, *vmin, *vmax variants, all equal to the window viewport), env(safe-area-inset-*, <fallback>), calc(), min(), max(), clamp(), var(--name, <fallback>). Expressions are compiled to bytecode and evaluated once per render with the window metrics; there is no per-frame style work.

Named colors, transparent, #rgb, #rgba, #rrggbb, #rrggbbaa, rgb()/rgba() (comma and space syntax), hsl()/hsla(), hwb(), lab(), lch(), oklab(), oklch(), color(srgb | srgb-linear | display-p3 …), color-mix(in srgb | srgb-linear | oklab | oklch, …). All colors resolve to ARGB at compile time (out-of-gamut values are clipped to sRGB). currentColor → — (use the same var() as color).

  • Type (Text), class, id, attribute ([a], =, ~=, |=, ^=, $=, *=) and * selectors; descendant and child (>) combinators.
  • :not(<simple selectors>), :is(), :where(), :matches() (expanded at compile time).
  • States: :pressed, :active, :focus, :focus-visible, :hover, :disabled, :checked, :selected, :loading, :error, :empty; comma-separated state lists are supported.
  • Pseudo-elements: ::placeholder and ::selection (color only).
  • Sibling combinators (+, ~), structural pseudo-classes (:first-child, :last-child, :nth-child() …) and other pseudo-elements → — (bind a class from the loop instead).
  • Cascade: !important, specificity, then source order. Repeated declarations inside one rule act as fallbacks (the last valid one wins).
  • Custom properties: declared in :root (global) or inside a rule (visible to that rule’s declarations), with var() fallbacks.
  • @import "relative.css"; (inside the Composer project).
  • @font-face with packaged fonts.
  • @media: width/height (min-/max-, comparison and range syntax), orientation, prefers-color-scheme, prefers-reduced-motion, pointer, display-mode and PAM device features; and, or, ,, not, only, screen/all/print; em/rem breakpoints. :root variables resolve inside query blocks.
  • @container [name] (<condition>) with the same condition grammar.
  • @supports (<declaration>), not, and, or, selector() — evaluated at compile time against the native compiler.
  • @layer, @keyframes, PAM @tokens and @recipe.
  • CSS nesting → —.

Accepted for shared web/native CSS and ignored because they have no native meaning: cursor, -webkit-tap-highlight-color, touch-action, will-change, contain, isolation, font smoothing, text-rendering, appearance, outline*, scroll-behavior, overscroll-behavior*, -webkit-overflow-scrolling, content-visibility, backface-visibility, resize, accent-color, color-scheme, -webkit-box-orient, unicode-bidi, tab-size, font-kerning, text-align-last, scrollbar properties.

  • Values are compiled once; the renderer receives typed integers/floats.
  • Each property has a StyleRenderCost (Composite, Paint, Layout) in StylePropertyCatalog. Only layout keys mark the Rust layout tree dirty; paint-only changes (colors, shadows, transforms, opacity) never trigger a relayout.
  • Transitions and keyframes animate on the native UI thread.
  • Gradients, shadow lists and filter matrices travel as compact strings, are decoded once per distinct value (LRU-cached), and their shaders/masks are rebuilt only when the painted size changes. Animating gradients is not supported; animate opacity/transforms of a gradient layer instead.

Text boxes are measured by the platform text stack that draws them (Android StaticLayout, iOS CoreText), following React Native: integer font pixel sizes on Android, lineHeight line boxes, numberOfLines with a tail ellipsis and real first baselines for align-items: baseline. Border widths inset children and content-sized boxes grow by their borders (Yoga semantics).

A Text may contain text and nested Text/Span runs. They render as one paragraph (one StaticLayout) and wrap together:

<Text class="message">
Olá <Text class="strong">mundo</Text>, veja
<Span class="link" on:press="openLink">https://zé.chat</Span> e fale com
<Span class="mention" on:press="openProfile">@ana</Span>
</Text>
.message { font-size: 15px; line-height: 20px; color: #111; }
.strong { font-weight: 700; }
.link { color: #1B7A4E; text-decoration: underline; }
.mention { color: #0055FF; background-color: #EEF3FF; font-size: 14px; }
  • Runs may change font-size, font-weight, font-style, font-family (including @font-face families), color, background-color, text-decoration, letter-spacing and text-transform; nested runs inherit from their ancestors.
  • Paragraph properties (line height, alignment, numberOfLines, ellipsis, padding) belong to the outer Text.
  • on:press on a run makes only that run pressable (links, mentions, hashtags). Presses on plain text fall through to the outer Text/parent.
  • Whitespace inside Text follows JSX: spaces on a line are kept, line breaks and indentation between runs collapse.

PHP API:

Text::rich(
'Olá ',
Text::make('mundo')->style(new Style(fontWeight: 700)),
' — ',
Text::make('@ana')->style(new Style(textColor: 0xFF0055FF))
->on(EventKind::Press, fn () => $this->openProfile('ana')),
);

React Native Android’s default is includeFontPadding: true: the first and last lines include the font’s top/bottom padding (for Roboto 14 sp at 2.625× the single-line box is 49 px instead of 43 px). PAM now uses the same default for Text and measures with it. Opt out per element:

.badge { include-font-padding: false; } /* or -pam-include-font-padding */
<Text includeFontPadding="false">12</Text>

Text::make('12')->includeFontPadding(false) does the same in PHP.

Syntax Value
hairline StyleSheet.hairlineWidth: PixelRatio.roundToNearestPixel(0.4), never 0 (1 physical px on 1×–3.5× screens)
Ndpx N physical pixels (N / density points)

They work anywhere a length is accepted, including calc() and border shorthands: border-bottom: hairline solid #DDD; height: 2dpx;. Pam\Native\PixelRatio exposes get(), getFontScale(), roundToNearestPixel(), getPixelSizeForLayoutSize() and hairlineWidth().

Layout keeps fractional points and the renderer snaps every edge to the physical pixel grid (absolute left/top/right/bottom rounded, like Yoga’s roundLayoutResultsToPixelGrid); text widths are whole pixels.

@font-face maps each weight of a family to its own file; when an exact file exists the weight is resolved to that file and no synthetic bold is applied:

@font-face { font-family: "Space Grotesk"; src: url(asset://fonts/SpaceGrotesk-Regular.ttf); font-weight: 400; }
@font-face { font-family: "Space Grotesk"; src: url(asset://fonts/SpaceGrotesk-Medium.ttf); font-weight: 500; }
@font-face { font-family: "Space Grotesk"; src: url(asset://fonts/SpaceGrotesk-SemiBold.ttf); font-weight: 600; }
@font-face { font-family: "Space Grotesk"; src: url(asset://fonts/SpaceGrotesk-Bold.ttf); font-weight: 700; }

Besides @font-face, a bare fontFamily resolves bundled files by React Native’s conventions: assets/fonts/{Family}-{Weight}.ttf (SpaceGrotesk-SemiBold), {Family}_{weight}, {Family}_bold/_italic and {Family}.ttf (spaces removed). An exact weight/style file is used as is (no synthetic bold); otherwise the closest file is synthesized like React Native.

<Icon font="Ionicons" name="chatbubble-ellipses" size="24" color="#1B7A4E" /> renders one glyph of a bundled icon font, exactly like react-native-vector-icons: a Text using the icon font, default size 12, black, allowFontScaling off, measured with the font’s own metrics (includeFontPadding included) and crisp at any size. CSS color and font-size apply as on text.

Bundle the font and its glyph map from react-native-vector-icons:

assets/fonts/Ionicons.ttf # react-native-vector-icons/Fonts/Ionicons.ttf
assets/fonts/Ionicons.json # react-native-vector-icons/glyphmaps/Ionicons.json

Any font name works by the same convention (MaterialCommunityIcons, Feather, …). Other locations: Icon::register('Brand', 'asset://assets/fonts/brand.ttf', 'assets/fonts/brand.json') (or an array name => codepoint). Unknown glyphs render ?. PHP: Icon::make($name, $font, $size, $color) and Icon::has().