| title | Tailwind reference |
|---|---|
| description | Every Tailwind utility and variant NSH resolves natively, the arbitrary value syntax, and how to extend the theme in nsh.json. |
| order | 17 |
Every element takes Tailwind CSS classes in its class prop (and conditional ones in classList). NSH resolves them in Rust against Tailwind 4's default theme: JS passes only the string, each distinct string is parsed once and cached, and variants such as dark: or active: are re-evaluated natively when the appearance, window size or press state changes, without running any JS.
Each utility sets exactly what Tailwind CSS 4 generates for it, with the same meaning as in a browser. This page lists what is supported. For an introduction see Tailwind; for the style keys the utilities set, see the Style reference.
Rust components take no class strings: they style elements with the typed methods of Styled (nsh_core::prelude), named after these utilities and resolving to the same values, Tailwind's default scales included (px-4 is .px_4(), rounded-2xl .rounded_2xl(), shadow-md .shadow_md(), bg-linear-to-br .bg_linear_to_br()). Colors are values: theme colors, or Tailwind's palette as nsh_core::element::palette (ZINC.shade(800) is zinc-800). Variants are methods taking a closure of styles: .active(|s| …), .hover(…), .focus(…), .disabled_style(…), .dark(…), .group_active(…), and .style_when(condition, …) for the rest. The Style reference names the method of each style key.
use nsh_core::{element::palette::{RED, ZINC}, prelude::*};
view()
.flex_row()
.items_center()
.gap_3()
.rounded_2xl()
.bg(white())
.p_4()
.shadow_md()
.active(|s| s.scale(0.98))
.transition()
.dark(|s| s.bg(ZINC.shade(800)))
.child(
text("Inbox")
.text_base()
.font_weight(FontWeight::SEMIBOLD)
.text_color(ZINC.shade(900))
.dark(|s| s.text_color(white())),
)
.child(
text(move || unread.get().to_string())
.text_color(move || if unread.get() > 0 { RED.shade(500) } else { ZINC.shade(400) }),
)<View class="flex-row items-center gap-3 rounded-2xl bg-white p-4 shadow-md active:scale-[0.98] transition dark:bg-zinc-800">
<Text class="text-base font-semibold text-zinc-900 dark:text-white">Inbox</Text>
<Text classList={{ "text-red-500": unread() > 0, "text-zinc-400": unread() === 0 }}>{unread()}</Text>
</View>- Later wins. Among utilities of the same variant level, the one later in the string wins:
"p-4 p-2"isp-2. A component can take aclassprop and append it to its own classes withouttailwind-merge. - Variants apply after plain utilities, in Tailwind's variant order:
group-*:(by their state, then by group name),selection:,placeholder:, position (first:...),checked:,indeterminate:,selected:,focus-within:,hover:,focus:,focus-visible:,active:,enabled:,disabled:,aria-busy:,aria-checked:,aria-expanded:,aria-pressed:,aria-selected:, motion,max-*:breakpoints widest first, then breakpoints ascending, orientation, direction,dark:, then platform. A class's highest variant decides, then its next highest, and one more variant comes later (dark:hover:afterdark:), as Tailwind sorts them. So"active:bg-red-500 bg-blue-500"is red while pressed. stylewins over classes. The inlinestyleis applied on top of the resolved classes. A style key is the CSS property of its name, the one the utilities set, so it replaces that property and nothing else:style={{ paddingLeft: 8 }}beatspl-4and keepspr-4. An important utility (!p-4orp-4!) wins over the inline style too.- Text styles inherit. Text utilities on a view (
text-white,font-bold,text-center, ...) pass to the<Text>and<Input>elements inside it, as in CSS. - Display is CSS's.
flexandinline-flexmake a row unless a direction utility says otherwise, wherever it is in the string (flex-col flexis a column). A style'sdisplay: "flex"is a row in the same way; views that set no display keep NSH's column default.
Lengths are points (1 = one point on iOS, one dp on Android). Tailwind's 1rem is 16 points, so the theme matches the web at the same density: one spacing step is 4 points (p-4 = 16 points), text-base is 16 points.
Utilities that take a spacing value (padding, margin, gap, inset, width, height, basis, translate, leading) accept:
| Value | Example | Result |
|---|---|---|
| Steps (quarter steps allowed) | p-4, gap-1.5, m-0.25 |
step × 4 points |
px |
p-px, -mt-px |
1 point |
0 |
m-0 |
0 |
Fraction (inset, width, height, basis, translate) |
w-1/2, left-1/3 |
percentage of the parent |
full (the same) |
w-full, inset-full |
100% |
auto (margin, inset, width, height, basis) |
mx-auto, w-auto |
"auto" |
| Arbitrary | p-[13px], w-[42%], mt-[1.5rem] |
the given length |
Margins, insets, translate, rotate, skew, scale, z, hue-rotate, tracking, underline-offset, outline-offset, grid lines and gradient angles take a leading - for negative values: -mt-2, -translate-x-1/2, -rotate-6. Tailwind has no negative form of the others (-p-4, -gap-2, -w-4), and neither has NSH.
The full Tailwind 4 palette is built in: red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose, slate, gray, zinc, neutral, stone, mauve, olive, mist, taupe, each in shades 50, 100–900, 950; plus black, white and transparent. Your own colors come from nsh.json (see Theme customization).
Every color utility takes:
| Form | Example |
|---|---|
| A theme color | bg-sky-500, text-brand |
| An opacity modifier (percent) | bg-black/50, ring-black/5 |
| An arbitrary opacity | text-black/[0.35] |
| An arbitrary color | bg-[#0ea5e9], border-[rgb(0_0_0_/_0.1)], text-[oklch(70%_0.2_50)] |
| An arbitrary color with a modifier | bg-[rgb(0_0_255)]/25 |
currentColor and inherit are not color values. As in a browser, a border, an outline or a ring without a color class is drawn in the element's text color, its own or the one it inherits (<View class="text-white"><View class="border ring-2" /></View> draws both white), and so is a border whose width the style prop sets without a color.
Square brackets pass a value the theme does not have: w-[37px], grid-cols-[200px_1fr], bg-[linear-gradient(45deg,red,blue)].
_stands for a space (grid-cols-[1fr_2fr]); write\_for a literal underscore.- Lengths: a bare number or
px(points),pt(4/3 of a point, as in CSS),rem(16 points), and%where percentages make sense. - Angles (
rotate-[...],hue-rotate-[...],bg-linear-[...]):deg,rad,grad,turn. - Times (
duration-[...],delay-[...]):msors; a bare number is milliseconds. - Type hints settle ambiguous values:
text-[length:22px]is a size,text-[color:#333]a color;border-[length:3px]likewise.
Arbitrary CSS properties ([mask-type:luminance]) are not supported.
| Class | Sets |
|---|---|
flex, inline-flex |
display: "flex", and a row unless a direction utility is present |
grid, inline-grid |
display: "grid" |
block, flow-root |
block layout or an independent block formatting context |
box-border, box-content |
dimensions include borders and padding, or content alone |
w/h/size-min, w/h/size-max, w/h/size-fit |
Taffy intrinsic sizes: min-content, max-content, fit-content |
inline, inline-block |
display: "block": what a browser computes for them in a flex or grid parent |
hidden |
display: "none" |
relative, absolute |
position |
static |
position: "static": in the flow, its insets ignored ; absolute descendants use their nearest positioned ancestor |
sticky |
position: "sticky": in the flow, it sticks top (left in a horizontal scroll view) within the scroll view around it while its parent is in view |
fixed |
position: "fixed": an element laid out in its root view's box (the window or its modal) above that root view's children, in place while what is around it scrolls |
inset-<n>, inset-x-<n>, inset-y-<n> |
top / right / bottom / left |
top-<n>, right-<n>, bottom-<n>, left-<n> |
the matching offset |
start-<n>, end-<n> |
start / end (direction-aware) |
z-<n>, z-auto, z-[<n>] |
zIndex (z-auto is 0) |
visible |
visibility: "visible" |
invisible, collapse |
visibility: "hidden" (keeps its space) |
overflow-hidden, overflow-clip, overflow-x-hidden, overflow-y-hidden |
overflow: "hidden" |
overflow-visible |
overflow: "visible" |
aspect-square, aspect-video, aspect-<a>/<b>, aspect-[<a>/<b>], aspect-auto |
aspectRatio (1, 16/9, a/b, none) |
Scrolling is an element, not a class: overflow-auto and overflow-scroll are not supported; use scroll.
| Class | Sets |
|---|---|
flex-row, flex-row-reverse, flex-col, flex-col-reverse |
flexDirection |
flex-wrap, flex-wrap-reverse, flex-nowrap |
flexWrap |
flex-1 |
grow 1, shrink 1, basis 0 |
flex-<n> |
grow n, shrink 1, basis 0 |
flex-auto |
grow 1, shrink 1, basis auto |
flex-initial |
grow 0, shrink 1, basis auto |
flex-none |
grow 0, shrink 0, basis auto |
flex-[<grow>], flex-[<grow>_<shrink>_<basis>] |
the CSS shorthand: flex-[2], flex-[3_0_50%] |
grow, grow-<n>, grow-[<n>] |
flexGrow (bare: 1) |
shrink, shrink-<n>, shrink-[<n>] |
flexShrink (bare: 1) |
basis-<n> |
flexBasis (spacing, fraction, full, auto, arbitrary) |
justify-start, justify-end, justify-center, justify-between, justify-around, justify-evenly, justify-stretch, justify-normal |
justifyContent |
items-start, items-end, items-center, items-baseline, items-stretch |
alignItems |
self-auto, self-start, self-end, self-center, self-stretch, self-baseline |
alignSelf |
content-start, content-end, content-center, content-between, content-around, content-evenly, content-stretch, content-normal |
alignContent |
justify-items-start, -end, -center, -stretch, -normal |
justifyItems |
justify-self-auto, -start, -end, -center, -stretch |
justifySelf |
place-content-center, -start, -end, -between, -stretch |
alignContent and justifyContent |
place-items-center, -start, -end, -stretch |
alignItems and justifyItems |
place-self-auto, -center, -start, -end, -stretch |
alignSelf and justifySelf |
gap-<n>, gap-x-<n>, gap-y-<n> |
gap, columnGap, rowGap |
space-x-<n>, space-y-<n> (and negative) |
a margin on each child but the last: the end margin (space-x) or the bottom one (space-y), under the child's own margins, as in Tailwind CSS; it adds to a gap |
space-x-reverse, space-y-reverse |
the margin on the start / top side instead (children in reverse order) |
grid-cols-<n> |
gridTemplateColumns: "repeat(n, minmax(0, 1fr))" |
grid-cols-[<tracks>], grid-cols-none |
gridTemplateColumns (grid-cols-[200px_1fr]) |
grid-rows-<n>, grid-rows-[<tracks>], grid-rows-none |
gridTemplateRows |
col-span-<n>, col-span-full, col-auto, col-<n>, col-[<value>] |
gridColumn |
col-start-<n>, col-end-<n> (and -auto) |
gridColumnStart, gridColumnEnd |
row-span-<n>, row-span-full, row-auto, row-<n>, row-[<value>] |
gridRow |
row-start-<n>, row-end-<n> (and -auto) |
gridRowStart, gridRowEnd |
grid-flow-row, grid-flow-col, grid-flow-dense, grid-flow-row-dense, grid-flow-col-dense |
gridAutoFlow |
auto-cols-auto, -min, -max, -fr, auto-cols-[<track>] |
gridAutoColumns |
auto-rows-auto, -min, -max, -fr, auto-rows-[<track>] |
gridAutoRows |
| Class | Sets |
|---|---|
p-<n>, px-<n>, py-<n> |
padding, paddingHorizontal, paddingVertical |
pt-<n>, pr-<n>, pb-<n>, pl-<n> |
paddingTop / Right / Bottom / Left |
ps-<n>, pe-<n> |
paddingStart, paddingEnd (direction-aware) |
m-<n>, mx-<n>, my-<n> |
margin, marginHorizontal, marginVertical |
mt-<n>, mr-<n>, mb-<n>, ml-<n> |
marginTop / Right / Bottom / Left |
ms-<n>, me-<n> |
marginStart, marginEnd (direction-aware) |
| Class | Sets |
|---|---|
w-<n>, h-<n> |
width, height (spacing, fraction, full, auto, arbitrary) |
size-<n> |
width and height |
min-w-<n>, max-w-<n>, min-h-<n>, max-h-<n> |
the matching limit |
w-screen, w-dvw, w-svw, w-lvw |
the window width (also on min-w- / max-w-) |
h-screen, h-dvh, h-svh, h-lvh |
the window height (also on min-h- / max-h-) |
w-auto, h-auto |
auto (sized by content) |
max-w-none, max-h-none |
no limit |
w-<container>, min-w-<container>, max-w-<container> |
a container width (below) |
Container widths (points): 3xs 256, 2xs 288, xs 320, sm 384, md 448, lg 512, xl 576, 2xl 672, 3xl 768, 4xl 896, 5xl 1024, 6xl 1152, 7xl 1280; prose is 65 times the width of the element's font's 0 (CSS's 65ch), as in Tailwind CSS.
| Class | Sets |
|---|---|
font-sans |
the system font |
font-serif, font-mono |
fontFamily: "serif" / "monospace" |
font-thin (100), font-extralight (200), font-light (300), font-normal (400), font-medium (500), font-semibold (600), font-bold (700), font-extrabold (800), font-black (900) |
fontWeight |
font-[<weight>], font-[<family>] |
a numeric weight, otherwise a family: font-[650], font-[Inter] |
italic, not-italic |
fontStyle |
normal-nums, small-caps, oldstyle-nums, lining-nums, tabular-nums, proportional-nums |
fontVariant (they combine; normal-nums resets the number forms) |
text-<size> sets the font size and Tailwind's matching line height, which a leading-* class replaces wherever it is in the string and whatever its variant (leading-tight md:text-lg keeps the tight leading):
| Class | Size | Line height |
|---|---|---|
text-xs |
12 | 16 |
text-sm |
14 | 20 |
text-base |
16 | 24 |
text-lg |
18 | 28 |
text-xl |
20 | 28 |
text-2xl |
24 | 32 |
text-3xl |
30 | 36 |
text-4xl |
36 | 40 |
text-5xl |
48 | 1× |
text-6xl |
60 | 1× |
text-7xl |
72 | 1× |
text-8xl |
96 | 1× |
text-9xl |
128 | 1× |
| Class | Sets |
|---|---|
text-<size>/<leading> |
size and line height: text-sm/6 (24 points), text-lg/tight |
text-[<length>] |
fontSize: text-[22px], text-[length:22px] |
text-<color> |
color |
leading-none (1), leading-tight (1.25), leading-snug (1.375), leading-normal (1.5), leading-relaxed (1.625), leading-loose (2) |
lineHeight as a multiple of the font size |
leading-<n>, leading-[<length>] |
lineHeight in points (leading-6 = 24) |
leading-[<number>] |
lineHeight as a multiple of the font size (leading-[1.5]), inherited as that multiplier; em and % compute a length at the element's font size before inheritance |
tracking-tighter (-0.05em), tracking-tight (-0.025em), tracking-normal (0), tracking-wide (0.025em), tracking-wider (0.05em), tracking-widest (0.1em) |
letterSpacing relative to the font size |
tracking-[<value>] |
letterSpacing: tracking-[0.2em], tracking-[1px] |
| Class | Sets |
|---|---|
text-left, text-center, text-right, text-justify |
textAlign |
text-start, text-end |
textAlign: "start" / "end": the start or end of the line in the layout direction |
align-baseline, align-text-top, align-text-bottom, align-middle |
verticalAlign (views and images inside a text) |
uppercase, lowercase, capitalize, normal-case |
textTransform |
truncate |
one line, tail ellipsis, no wrapping, overflow: hidden (on a view: its texts do not wrap) |
line-clamp-<n>, line-clamp-none |
numberOfLines (none shows every line) |
text-ellipsis, text-clip |
textOverflow: "ellipsis" / "clip" |
whitespace-nowrap, text-nowrap |
lines never wrap; on a view, the lines of the texts in it (inherited, as CSS's white-space) |
whitespace-normal, text-wrap, text-balance, text-pretty |
lines wrap (balancing is not done) |
select-text, select-none |
userSelect: "text" / "none"; on a view, of the texts in it that set none |
select-auto |
userSelect: "auto": the view around it decides, as CSS's user-select: auto (select-none md:select-auto) |
| Class | Sets |
|---|---|
underline, line-through, no-underline |
textDecorationLine |
decoration-solid, decoration-double, decoration-dotted, decoration-dashed, decoration-wavy |
textDecorationStyle |
decoration-<n>, decoration-[<length>] |
textDecorationThickness |
decoration-auto, decoration-from-font |
textDecorationThickness: "auto" |
decoration-<color> |
textDecorationColor |
underline-offset-<n>, underline-offset-[<length>], underline-offset-auto |
textUnderlineOffset |
text-shadow-2xs, -xs, -sm, -md, -lg; /<alpha> |
a text shadow from the theme (one layer: the widest of the theme's); the modifier is the layers' alpha: text-shadow-md/20 |
text-shadow-<color>, text-shadow-current |
its color, at the size's alpha |
text-shadow-none |
removes it |
Tailwind's text shadows have several layers; natively one shadow is drawn, the widest layer.
| Class | Sets |
|---|---|
placeholder:text-<color> |
placeholderColor |
selection:bg-<color> |
selectionColor (the highlight of an input's or a selectable text's selection, and of those inside the element) |
caret-<color> |
the cursor color |
| Class | Sets |
|---|---|
bg-<color> |
backgroundColor |
bg-[<gradient>] |
backgroundImage: bg-[linear-gradient(45deg,red,blue)], bg-[radial-gradient(...)] |
bg-linear-to-t, -tr, -r, -br, -b, -bl, -l, -tl |
a linear gradient toward a side or corner (bg-gradient-to-* is the v3 name) |
bg-linear-<angle>, bg-linear-[<angle>] |
a linear gradient at an angle: bg-linear-45, -bg-linear-12, bg-linear-[0.25turn] |
bg-radial |
a radial gradient from the center |
bg-conic, bg-conic-<angle> |
a conic gradient, optionally starting at an angle |
from-<color>, via-<color>, to-<color> |
the gradient's stops (default transparent at the ends) |
from-<p>%, via-<p>%, to-<p>% |
stop positions: via-40%, from-[15%] |
bg-none |
removes the gradient |
Gradient utilities mix their colors in Oklab, as Tailwind CSS writes them (in oklab); at an arbitrary position (bg-linear-[25deg], bg-conic-[0.25turn]) they follow CSS's default, Oklab with the palette's colors and sRGB when every stop is a hex, rgb(), hsl() or named color.
view()
.h_32()
.rounded_2xl()
.bg_linear_to_br()
.gradient_from(INDIGO.shade(500))
.gradient_via(PURPLE.shade(500))
.gradient_via_position(percentage(0.4))
.gradient_to(PINK.shade(500))<View class="h-32 rounded-2xl bg-linear-to-br from-indigo-500 via-purple-500 via-40% to-pink-500" />| Class | Sets |
|---|---|
border, border-<n>, border-[<length>] |
borderWidth (bare: 1) |
border-x, border-y, border-t, border-r, border-b, border-l, border-s, border-e (with -<n> or bare) |
the matching sides' widths |
border-<color>, border-t-<color>, ... |
borderColor and the per-side colors |
border-solid, border-dashed, border-dotted |
borderStyle |
border-none, border-hidden |
borderStyle: "none" / "hidden": no border whatever the width classes, in any order (md:border-solid shows it again) |
rounded, rounded-<size>, rounded-none, rounded-full, rounded-[<length>] |
borderRadius (bare: sm) |
rounded-t-, -r-, -b-, -l-, -tl-, -tr-, -br-, -bl- |
the matching corners |
rounded-s-, -e-, -ss-, -se-, -es-, -ee- |
start / end corners (direction-aware): borderTopStartRadius, borderTopEndRadius, borderBottomStartRadius, borderBottomEndRadius |
outline, outline-<n>, outline-[<length>] |
outlineWidth (bare: 1), solid unless a style class is anywhere in the string (outline-dashed outline-2 is dashed, outline-none outline-2 draws nothing) |
outline-<color> |
outlineColor |
outline-solid, outline-dashed, outline-dotted, outline-none, outline-hidden |
outlineStyle; a style alone is CSS's medium width, 3 points |
outline-offset-<n>, outline-offset-[<length>] |
outlineOffset |
Radius sizes (points): xs 2, sm 4, md 6, lg 8, xl 12, 2xl 16, 3xl 24, 4xl 32, full 9999.
Rings are box shadows with a spread, drawn outside the border without taking space.
| Class | Sets |
|---|---|
ring, ring-<n>, ring-[<length>] |
a ring of that width (bare: 1) |
ring-<color> |
its color (default the element's text color) |
ring-inset |
draws the ring inside |
ring-offset-<n>, ring-offset-<color> |
a gap between element and ring (default white) |
inset-ring, inset-ring-<n>, inset-ring-<color> |
an inner ring |
| Class | Sets |
|---|---|
shadow-2xs, shadow-xs, shadow-sm, shadow, shadow-md, shadow-lg, shadow-xl, shadow-2xl; /<alpha> |
boxShadow from the theme; the modifier is the layers' alpha: shadow-xl/20 |
shadow-[<box-shadow>] |
an arbitrary shadow: shadow-[0_8px_24px_rgb(0_0_0_/_0.12)] |
shadow-<color>, shadow-current |
recolors the shadow layers, at the size's alpha: shadow-indigo-500/40 |
shadow-none |
no shadow |
inset-shadow-2xs, -xs, -sm, inset-shadow, inset-shadow-[...], inset-shadow-<color>, inset-shadow-none |
an inner shadow |
opacity-<n>, opacity-[<value>] |
opacity: opacity-50 = 0.5 |
mix-blend-<mode> |
mixBlendMode: normal, multiply, screen, overlay, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion, hue, saturation, color, luminosity, plus-darker, plus-lighter |
Shadows, rings and inset shadows combine into one boxShadow list, as in CSS. A shadow color replaces each layer's color, alpha too, as Tailwind's --tw-shadow-color: shadow-md shadow-red-500 is an opaque red shadow, shadow-md/20 shadow-red-500 one at 20%.
| Class | Sets |
|---|---|
blur, blur-xs (4), -sm (8), -md (12), -lg (16), -xl (24), -2xl (40), -3xl (64), blur-none, blur-[<length>] |
blur() |
brightness-<n>, contrast-<n>, saturate-<n> |
percentage factors: brightness-125 = 1.25 |
grayscale, grayscale-<n>, invert, invert-<n>, sepia, sepia-<n> |
bare: 100% |
hue-rotate-<deg>, -hue-rotate-<deg> |
hue-rotate() |
drop-shadow, drop-shadow-xs, -sm, -md, -lg, -xl, -2xl, drop-shadow-[...], drop-shadow-none; drop-shadow-<color>, drop-shadow-current; /<alpha> on either |
a drop-shadow() per layer, after the other filters; a color replaces the layers', at the size's alpha; the web, Linux, Windows and GPUI draw every layer, iOS, macOS and Android the widest |
drop-shadow-none |
removes it |
filter-none |
removes all filters |
backdrop-blur, backdrop-blur-<size>, backdrop-blur-[<length>] |
blurs what is behind the view |
backdrop-brightness-<n>, backdrop-contrast-<n>, backdrop-grayscale, backdrop-hue-rotate-<deg>, backdrop-invert, backdrop-opacity-<n>, backdrop-saturate-<n>, backdrop-sepia |
backdrop filters |
backdrop-filter-none |
removes them |
Warning
iOS applies only opacity, brightness up to 1 and drop-shadow to views; <Image> takes every color filter and blur. Android needs API 31 for blur, drop-shadow and backdrop blur. See the Style reference.
| Class | Sets |
|---|---|
translate-x-<n>, translate-y-<n>, translate-<n> |
translate (spacing, full, fractions of the element's own size: -translate-x-1/2, arbitrary) |
rotate-<deg>, rotate-[<angle>] |
rotate |
rotate-x-<deg>, rotate-y-<deg>, rotate-z-<deg> |
3D rotation (in transform, after the translation, the rotation and the scale) |
scale-<pct>, scale-x-<pct>, scale-y-<pct>, scale-[<factor>] |
scale: scale-95 = 0.95, -scale-x-100 mirrors |
skew-<deg>, skew-x-<deg>, skew-y-<deg> |
skew, in transform (not on Android) |
perspective-dramatic (100), -near (300), -normal (500), -midrange (800), -distant (1200), perspective-[<length>], perspective-none |
depth for the 3D rotations of the element's children, seen from its center (CSS's perspective: <View class="perspective-near"><View class="rotate-y-45" /></View>) |
origin-center, -top, -top-right, -right, -bottom-right, -bottom, -bottom-left, -left, -top-left, origin-[<value>] |
transformOrigin |
backface-hidden, backface-visible |
backfaceVisibility |
transform-none |
clears the 3D rotations and the skews (CSS's transform: none) |
translate-none, rotate-none, scale-none |
clears the translation, the rotation, the scale |
Transform utilities compose like CSS: translate-*, rotate-* and scale-* set the translate, rotate and scale properties and combine regardless of order, then transform applies (rotate-x-*, skew-*). The style prop's keys of the same names replace only their own property: style={{ scale: 1.1 }} replaces scale-95 and keeps rotate-3. transform, transform-gpu, transform-cpu and transform-flat are accepted and do nothing: NSH composes every transform alike, flat. Translations by a fraction of the size are resolved against the element's frame and follow it when it resizes.
| Class | Sets |
|---|---|
transition |
colors, opacity, shadow and transform; 150 ms, cubic-bezier(0.4, 0, 0.2, 1) |
transition-all, transition-colors, transition-opacity, transition-shadow, transition-transform |
the animated properties |
transition-none |
no transition |
duration-<ms>, duration-[<time>] |
transitionDuration: duration-300, duration-[0.4s] |
delay-<ms>, delay-[<time>] |
transitionDelay |
ease-linear, ease-in, ease-out, ease-in-out, ease-initial, ease-[cubic-bezier(...)] |
transitionTimingFunction |
animate-spin, animate-ping, animate-pulse, animate-bounce |
a native keyframe animation, repeated forever |
animate-none |
stops it |
Transitions run on the UI thread when a value changes, including when a variant starts or stops matching, so transition active:scale-95 animates the press without JS. Wrap animations in motion-safe: to respect the reduce-motion setting.
| Class | Sets |
|---|---|
pointer-events-none, pointer-events-auto |
pointerEvents: "none" / "auto": whether the view is a target for touches and the pointer, inherited as in CSS: inside a pointer-events-none view, a pointer-events-auto one is a target again |
cursor-<name>, cursor-[<name>] |
cursor: cursor-pointer, cursor-grab, cursor-not-allowed, ... (every CSS cursor keyword) |
select-none, select-text, select-auto |
text selection (see Typography) |
| Class | Sets |
|---|---|
fill-<color>, fill-none, fill-current, fill-inherit |
fill (fill-current is currentColor) |
stroke-<color>, stroke-none, stroke-current, stroke-inherit |
stroke |
stroke-<number>, stroke-[<length>] |
strokeWidth |
| Class | Sets |
|---|---|
object-cover, object-contain, object-fill, object-none, object-scale-down |
objectFit (fill stretches, none keeps the natural size, placed at objectPosition, scale-down is the same, shrunk to fit where larger); images and videos |
object-top, object-bottom, object-left, object-right, object-center, object-top-left, object-top-right, object-bottom-left, object-bottom-right, object-[<position>] |
objectPosition (object-left-top, ... are the same as object-top-left, ...) |
tint-<color> |
tintColor (recolors template images) |
These utilities are rendering hints, or what every native view already is; they parse without a warning and change nothing: antialiased, subpixel-antialiased, isolation-auto, box-border, appearance-none, appearance-auto, will-change-auto, will-change-scroll, will-change-contents, will-change-transform, will-change-[<value>], transform, transform-gpu, transform-cpu, transform-flat. Scrolling, snapping and overscroll are props of scroll.
peer-* variants, pseudo-elements (before:, after:), arbitrary properties ([prop:value]), overflow-auto / overflow-scroll, divide-*, columns-*, float-*, container, container queries, order-*, transform-3d, select-all, border-double / outline-double, intrinsic min/max constraints (such as max-w-max), snap-*, scroll-*, overscroll-*, touch-* and isolate are unknown classes. nsh build and nsh dev warn about unknown classes they find in your source (file:line:col: unknown class '...'), and the runtime logs each unknown class once.
Variants make a utility conditional: dark:bg-zinc-900, md:flex-row, active:opacity-70. They stack (dark:active:bg-zinc-700) and are evaluated natively whenever what they depend on changes.
| Variant | Applies when | Rust |
|---|---|---|
dark: |
the appearance is dark | .dark(|s| …) |
ios: |
running on iOS or Mac Catalyst | .when(os() == Os::Ios, …) |
android: |
running on Android | .when(os() == Os::Android, …) |
portrait:, landscape: |
the window is taller than wide / wider than tall | .style_when(move || window().height > window().width, …) |
ltr:, rtl: |
the layout direction | .style_when(rtl, …) |
motion-safe:, motion-reduce: |
the user has not / has asked for reduced motion | .style_when(reduce_motion, …) |
Breakpoints compare the window width in points.
| Variant | Min width |
|---|---|
sm: |
640 |
md: |
768 |
lg: |
1024 |
xl: |
1280 |
2xl: |
1536 |
| Form | Example | Applies when |
|---|---|---|
<bp>: |
md:p-8 |
width ≥ breakpoint |
max-<bp>: |
max-md:p-1 |
width < breakpoint |
min-[<length>]: |
min-[600px]:grid-cols-3 |
width ≥ the length |
max-[<length>]: |
max-[400px]:text-sm |
width < the length |
A phone in portrait (390 points wide) matches none of the default breakpoints; a tablet or a phone in landscape matches several. In Rust, a breakpoint is a condition on the window: .style_when(move || window().width >= px(768.0), |s| s.p_8()) is md:p-8.
States are tracked by the platform only for elements whose classes use them, and changes apply natively, with no JS round trip.
| Variant | Applies when | Rust |
|---|---|---|
active: |
the element is pressed | .active(|s| …) |
hover: |
a pointer is over it (iPad pointer, Mac Catalyst, Android mouse) | .hover(|s| …) |
focus:, focus-visible: |
the input has focus | .focus(|s| …) |
focus-within: |
the element or a descendant has focus (an Overlay's content is a descendant of the element it is in) |
.focus_within(…) |
disabled: |
the element has the disabled prop |
.disabled_style(|s| …) |
enabled: |
the element does not have the disabled prop |
|
selected: |
the element is selected: a Button's selected, or accessibilityState.selected |
|
checked: |
the element is checked: accessibilityState.checked is true ("mixed" is not checked) |
|
indeterminate: |
the element is indeterminate: accessibilityState.checked is "mixed" |
|
aria-expanded: |
the element is expanded: accessibilityState.expanded is true (a Popover's or Menu's trigger while its popup is open) |
|
aria-pressed: |
the element is a toggle button that is on: accessibilityState.pressed is true ("mixed" is not pressed; not active:, a press in progress) |
.aria_pressed(|s| …) |
aria-busy: |
the element is busy: accessibilityState.busy is true |
|
aria-checked: |
the element is checked: accessibilityState.checked is true, as checked: |
|
aria-selected: |
the element is selected: accessibilityState.selected is true, as selected: |
The same states as style objects are the activeStyle, hoverStyle, focusStyle, disabledStyle, checkedStyle, indeterminateStyle, expandedStyle and pressedStyle props of every element (and a Button's selectedStyle); they go over the element's style and classes.
disabled works on any element: it applies disabled: and removes the element's press handlers, so it shows no press feedback either.
view()
.on_press(save)
.disabled(saving)
.rounded_lg()
.bg(BLUE.shade(600))
.px_4()
.py_2()
.transition()
.active(|s| s.scale(0.95).bg(BLUE.shade(700)))
.disabled_style(|s| s.opacity(0.4))
.child(text("Save").font_weight(FontWeight::MEDIUM).text_color(white()))<View
onPress={save}
disabled={saving()}
class="rounded-lg bg-blue-600 px-4 py-2 transition active:scale-95 active:bg-blue-700 disabled:opacity-40"
>
<Text class="font-medium text-white">Save</Text>
</View>Mark an ancestor with group and style descendants by its state with group-<state>:. Name groups to nest them: group/card and group-hover/card:.
| Variant | Applies when the group is | Rust |
|---|---|---|
group-active: |
pressed | .group_active(…), .group_active_named(name, …) |
group-hover: |
hovered | .group_hover(…), .group_hover_named(name, …) |
group-focus:, group-focus-visible: |
focused | |
group-focus-within: |
focused or containing focus | .group_focus_within(…), .group_focus_within_named(name, …) |
group-disabled: |
disabled | |
group-selected: |
selected | |
group-checked: |
checked | |
group-indeterminate: |
indeterminate | |
group-aria-expanded: |
expanded | |
group-aria-pressed: |
a toggle button that is on | |
group-aria-busy:, group-aria-checked:, group-aria-selected: |
busy, checked, selected |
view()
.on_press(open)
.group_named("row")
.flex_row()
.items_center()
.gap_3()
.p_3()
.active(|s| s.bg(ZINC.shade(100)))
.child(
text("Settings")
.flex_1()
.text_color(ZINC.shade(900))
.group_active_named("row", |s| s.text_color(BLUE.shade(600))),
)
.child(
text("›")
.text_color(ZINC.shade(400))
.group_active_named("row", |s| s.translate_x(px(4.0)))
.transition_transform(),
)<View onPress={open} class="group/row flex-row items-center gap-3 p-3 active:bg-zinc-100">
<Text class="flex-1 text-zinc-900 group-active/row:text-blue-600">Settings</Text>
<Text class="text-zinc-400 group-active/row:translate-x-1 transition-transform">›</Text>
</View>| Variant | Applies to |
|---|---|
first: |
the first child |
last: |
the last child |
only: |
the only child |
odd: |
the 1st, 3rd, ... child |
even: |
the 2nd, 4th, ... child |
each(items, |item: &Item| item.id, move |item, index| {
text(move || item.with(|i| i.name.clone()))
.border_b(px(1.0))
.border_color(ZINC.shade(200))
.py_3()
.style_when(move || index.get() + 1 == items.with(Vec::len), |s| s.border_b(px(0.0)))
})<Each of={items()}>{(item) => <Text class="border-b border-zinc-200 py-3 last:border-b-0">{item().name}</Text>}</Each>placeholder: applies to an <Input>'s placeholder and only takes a text color: placeholder:text-zinc-400 (the style key placeholderColor). selection: applies to the selection of an <Input> or a selectable <Text>, and on any element to those inside it (Tailwind's & *::selection; the nearest element's wins), and takes its background, the highlight: selection:bg-violet-200 (the style key selectionColor, which also tints an input's caret where the platform draws them in one color).
! before or after a utility (!p-4, p-4!) makes it win over every other class and over the inline style.
Extend the theme in the "tailwind" object of your app's nsh.json. Entries with a new name are added; entries with an existing name replace the default. Lengths are points. The theme is checked when the app is bundled: an unknown key or an invalid value is a build error.
{
"name": "My App",
"id": "com.example.myapp",
"tailwind": {
"colors": {
"brand": { "50": "#f5f3ff", "500": "#7c3aed", "600": "#6d28d9", "DEFAULT": "#7c3aed" },
"accent": "#f59e0b"
},
"spacing": 4,
"breakpoints": { "tablet": 600 },
"fontFamily": { "display": "Inter" },
"fontSize": { "huge": [48, 1.1] },
"fontWeight": { "heavy": 850 },
"radius": { "card": 14 },
"shadow": { "card": "0 2px 8px rgb(0 0 0 / 0.12)" },
"insetShadow": { "well": "inset 0 2px 6px rgb(0 0 0 / 0.1)" },
"dropShadow": { "icon": "0 2px 4px rgb(0 0 0 / 0.2)" },
"tracking": { "label": 0.08 },
"leading": { "dense": 1.15 },
"blur": { "glass": 20 },
"perspective": { "card": 700 },
"aspect": { "photo": 1.5 },
"ease": { "snappy": [0.2, 0, 0, 1] },
"container": { "content": 680 }
}
}With this theme, bg-brand-500, bg-brand (the DEFAULT shade), text-accent, tablet:flex-row, font-display, text-huge, font-heavy, rounded-card, shadow-card, inset-shadow-well, drop-shadow-icon, tracking-label, leading-dense, blur-glass, perspective-card, aspect-photo, ease-snappy and max-w-content all work.
| Key | Value | Used by |
|---|---|---|
colors |
{ name: color } or { name: { shade: color } }; DEFAULT names the bare color. Any CSS color syntax. |
every color utility |
spacing |
points per step (default 4) | p-*, m-*, gap-*, w-*, ... |
rem |
points per rem (default 16) |
|
breakpoints |
{ name: width } |
breakpoint variants (sorted by width) |
container |
{ name: width } |
w-*, min-w-*, max-w-* |
fontFamily |
{ name: family } (a system family or the name of a bundled font) |
font-* |
fontSize |
{ name: size } or { name: [size, lineHeightMultiple] } |
text-* |
fontWeight |
{ name: 1–1000 } |
font-* |
tracking |
{ name: em } |
tracking-* |
leading |
{ name: multiple } |
leading-* |
radius |
{ name: points } |
rounded-* |
shadow, insetShadow, dropShadow |
{ name: "CSS box-shadow" } |
shadow-*, inset-shadow-*, drop-shadow-* |
blur |
{ name: points } |
blur-*, backdrop-blur-* |
perspective |
{ name: points } |
perspective-* |
aspect |
{ name: ratio } |
aspect-* |
ease |
{ name: [x1, y1, x2, y2] } |
ease-* |
Note
The Tailwind theme shapes only your app's classes. The app's design tokens (shadcn/ui's colors, radius, the fonts and the app's own) are its theme, the "theme" key of nsh.json, which Rust components draw with; see Rust. Classes name those tokens as Tailwind names @theme variables (bg-primary, bg-up/30, rounded-(--radius)): see Theme tokens.
- Tailwind: the guide.
- Styling:
class,classList,styleand how they combine. - Style reference: the style keys utilities set.