Complete reference for all Tailwind-style utility classes in Whitehall, with equivalent props and generated Kotlin/Compose output.
Prefer classes over props. Classes are faster to write, more scannable, and consistent with web conventions.
Use props only for dynamic/computed values.
// Preferred: class syntax
<Column class="p-16 gap-8 items-center bg-primary rounded-lg">
<Text class="text-lg font-bold">Title</Text>
</Column>
// Also works: prop syntax (use for dynamic values)
<Column p={dynamicPadding} gap={8}>
<Text>Title</Text>
</Column>
Scale: Classes and props use a 1:1 scale. The number IS the dp value: p-16 = p={16} = 16dp
Override: Props override class values: class="p-4" p={8} results in 8dp padding
Color classes read the theme. With a theme preset, bg-*, text-*, border-* and the rounded-* scale resolve against the project's tokens at runtime — so bg-card works, and theme="auto" repaints the app without a rebuild. Without one they fall through to
Material roles and the Tailwind palette, exactly as documented below.
See it happen
Watch the chain change as classes are added.
Card.wh
<Box class="bg-primary">
<Text>Card</Text>
</Box>
Kotlinwaiting
Scroll into view to compile.
One class, one call: bg-primary becomes a single .background(...) on the Modifier chain.
Colors named for the job they do rather than the hue they are. These work under every preset, and they are what to reach for when a color should
follow the app's theme instead of being pinned to a value.
Name
What it is
background / foreground
The page, and text on it
card / card-foreground
A raised surface, and text on it
popover / popover-foreground
A floating surface, and text on it
primary / primary-foreground
The main action, and text on it
secondary / secondary-foreground
A quieter action, and text on it
muted / muted-foreground
A recessed fill, and secondary text
accent / accent-foreground
A hover or active fill, and text on it
destructive / destructive-foreground
Danger, and text on it
border
A divider or an outline
input
A field's edge, a step stronger than a border
ring
A focus ring
Each takes the usual prefixes, and the usual opacity suffix:
<Box class="bg-primary">
<Box class="bg-[#FF5722]">
<Box class="bg-yellow"> // yellow-500
<Box class="bg-yellow-100"> // light yellow
<Box class="bg-red-500/20"> // red at 20% opacity
Gradients
Class
Purpose
bg-gradient-to-r, -l, -t, -b
Linear gradient along an axis
bg-gradient-to-tr, -tl, -br, -bl
Linear gradient along a diagonal
from-{color}
First stop
via-{color}
Optional middle stop
to-{color}
Last stop
Compiles to Brush.linearGradient(...), with the direction suffix mapped to Offsets. Stop colors use the same grammar as bg-* — theme tokens,
Material roles, the Tailwind palette, arbitrary hex — each with the same /opacity suffix. A missing from-*/to-* stop defaults to
transparent, matching Tailwind. bg-gradient-to-* wins over a plain bg-* on the same element, the way a CSS background-image layers over
a background-color.
A background can be an image as well as a colour or a gradient. The image itself is one
value; everything about how it is drawn is its own class, the way Tailwind splits CSS's
shorthand.
Positioned against the window, or against the element
Paths resolve against static/ at build time. backgroundPositionX and backgroundPositionY are morphable, which is how a background parallaxes against a
scroll.
Put the image on the prop, not in a class. Tailwind's arbitrary-value form bg-[url(/photo.jpg)] is documented but does not currently compile: the class is
split on / to find an opacity — Tailwind's bg-red-500/20 spelling —
before the brackets are considered, so the path is read as an alpha value and rejected.
Since a static/ path starts with /, that is most of them.
Blur, Blend and Clipping
blur-*
Blurs the element's own content — its fill, its border, anything inside it. Tailwind's names
and Tailwind's values.
Class
Radius
blur-xsblur-smblur-mdblur-lg
4 / 8 / 12 / 16 dp
blur-xlblur-2xlblur-3xl
24 / 40 / 64 dp
blur
8 dp — Tailwind v3's default, kept as an alias
blur-none
Emits nothing
blur-[17]blur-[17px]
17 dp
Like CSS's filter: blur(), it spreads outside the element's box. A rounded-* or overflow-hidden on the same element clips it back,
exactly as overflow: hidden does on the web.
backdrop-blur-*
Blurs what is painted behind the element — a frosted panel over a photo or a
scrolling list. Same scale, same names.
Nothing marks the content being sampled. The compiler works out what to blur: everything
painted below the element in the same markup tree — preceding siblings at every level up the
chain, which is CSS's own definition of a backdrop.
Put the class where the backdrop is visible. A component whose own root
says backdrop-blur-xl with nothing behind it in its own file is a build error.
Pass the class at the use site instead — <FrostedCard class="backdrop-blur-xl" />.
Name a pair when a screen has more than one, or to bound what gets
recorded: backdrop/photo on the source, backdrop-blur-xl/photo on
the panel. Tailwind's group/edit spelling.
A child does not sample its own parent — recording an ancestor would
record the consumer inside it. Put a background meant to be blurred beside the
panel, not around it.
mix-blend-*
Blends the element with what is beneath it. Seventeen of Tailwind's eighteen names, mapped to
Compose's BlendMode.
The lowering is picked per element: a fill with no children blends straight into the parent's
canvas — one draw, no buffer — and anything else gets an offscreen layer, which is
unavoidable, since you cannot blend a composite that does not exist yet.
mix-blend-plus-darker has no Compose equivalent and is a build error rather than
a silent no-op. And mix-blend-* on an element that something below it blurs is
refused: being recorded gives it a canvas of its own, so the blend would quietly do nothing.
overflow-hidden
Clips children to the element's bounds, including a blur-* that would otherwise
spread past them. Pairs with rounded-* to clip to the corner radius.
A bare border is Tailwind's own default weight, 1 px — one physical
pixel, not a flat 1.dp. At a fractional density (2.625× on the reference device) a
flat 1.dp line rounds up to 3 physical pixels, nearly three times the hairline the
browser renders. (1f / density).dp converts back to exactly one physical pixel
whatever the density is. Every other width is a literal dp value; the overshoot is only
dramatic at 1px.
Directional Borders
Class
Effect
border-t
Top edge only, one physical pixel
border-b
Bottom edge only, one physical pixel
border-l
Left edge only, one physical pixel
border-r
Right edge only, one physical pixel
border-x
Left and right edges
border-y
Top and bottom edges
border-t-2, border-b-4, …
Any edge with an explicit dp width
There is no per-edge Modifier.border in Compose, so a directional border compiles
to a drawBehind that strokes only the requested edges. The border reserves its own space, the way a border-box edge adds to a content-sized
element's height on the web: each active edge also gets a .padding() (for t/b) or .absolutePadding() (for l/r)
— absolute, not start/end, because Tailwind's l/r are the physical sides, not the logical ones ps-/pe- already claim.
<Box class="border-b p-16"> // hairline bottom rule
<Row class="border-t-2 border-primary"> // 2dp top rule, theme color
<Column class="border-x-4"> // 4dp on both left and right
Border Colors
Class
Compose Output
border-primary
.border(..., MaterialTheme.colorScheme.primary)
border-outline
.border(..., MaterialTheme.colorScheme.outline)
border-white
.border(..., Color.White)
border-black
.border(..., Color.Black)
border-yellow
.border(..., Color(0xFFEAB308))
border-blue-300
.border(..., Color(0xFF93C5FD))
border-black/10
.border(..., Color(0x1A000000))
Edges share whatever border-{color} sets (or MaterialTheme.colorScheme.outline by default). There is no per-edge color —
Tailwind's border-t-red-500 has no equivalent here.
With a theme, shadow-* is Tailwind's real box shadow — offset, blur, spread and
color, most steps stacking two layers. Without one it stays the Material elevation it always
was, so nothing that predates themes changes.
Class
CSS (Tailwind v4)
Elevation under preset = "none"
shadow-none
none
No shadow
shadow-2xs
0 1px rgb(0 0 0 / 0.05)
No shadow
shadow-xs
0 1px 2px 0 rgb(0 0 0 / 0.05)
2dp
shadow-sm
0 1px 3px 0 …, 0 1px 2px -1px …
2dp
shadow
Tailwind v3's shadow, which v4 renamed shadow-sm
4dp
shadow-md
0 4px 6px -1px …, 0 2px 4px -2px …
6dp
shadow-lg
0 10px 15px -3px …, 0 4px 6px -4px …
10dp
shadow-xl
0 20px 25px -5px …, 0 8px 10px -6px …
16dp
shadow-2xl
0 25px 50px -12px rgb(0 0 0 / 0.25)
24dp
Elevation and box shadow do not convert. Android's shadow is a height: you give it a number and the platform derives blur and offset from a fixed
light position. CSS gives you the shadow directly, and Tailwind's scale uses all four
parameters including negative spread. shadow-lg is not "10dp of elevation" — it
is two specific shadows. Compose 1.9's Modifier.dropShadow takes exactly CSS's
parameters, so the scale is carried across as written rather than approximated.
The bare shadow keeps pointing at the same visual weight it always had, so
existing projects do not silently change when v4 shifted the whole scale down a step.
Opacity
Class
Alpha
Compose Output
opacity-0
0%
.alpha(0f)
opacity-25
25%
.alpha(0.25f)
opacity-50
50%
.alpha(0.5f)
opacity-75
75%
.alpha(0.75f)
opacity-100
100%
.alpha(1f)
<Card class="shadow-lg opacity-75">
Safe Area
Handle system UI insets (status bar, navigation bar):
Class
Prop
Compose Output
safe
safeArea
.windowInsetsPadding(WindowInsets.systemBars)
safe-top
safeTop
.windowInsetsPadding(WindowInsets.statusBars)
safe-bottom
safeBottom
.windowInsetsPadding(WindowInsets.navigationBars)
<Column class="safe-top p-16"> // Avoid status bar overlap
<Column class="safe"> // Avoid both bars
<Column safeTop safeBottom> // Prop syntax
Typography
Font Size
Class
sp Value
Compose Output
text-xs
12sp
fontSize = 12.sp
text-sm
14sp
fontSize = 14.sp
text-base
16sp
fontSize = 16.sp
text-lg
18sp
fontSize = 18.sp
text-xl
20sp
fontSize = 20.sp
text-2xl
24sp
fontSize = 24.sp
text-3xl
30sp
fontSize = 30.sp
text-4xl
36sp
fontSize = 36.sp
Font Weight
Class
Compose Output
font-thin
FontWeight.Thin
font-light
FontWeight.Light
font-normal
FontWeight.Normal
font-medium
FontWeight.Medium
font-semibold
FontWeight.SemiBold
font-bold
FontWeight.Bold
font-extrabold
FontWeight.ExtraBold
font-black
FontWeight.Black
All nine weights are declared against the theme font's wght axis, so font-bold gets the typeface's real bold rather than a synthesized smear of the
Regular master.
Font Family
Class
Compose Output
font-mono
fontFamily = FontFamily.Monospace
FontFamily.Monospace is a Compose constant, not a project setting — the font key in Theming configures the body typeface, which is a different axis, so font-mono does not go through the theme
layer. font-sans stays unmapped: in Tailwind it undoes a browser default Compose
never had.
Letter Spacing
Class
Value
tracking-tighter
-0.05em
tracking-tight
-0.025em
tracking-normal
0em
tracking-wide
0.025em
tracking-wider
0.05em
tracking-widest
0.1em
Tailwind's scale is in em, which maps directly onto Compose's TextStyle.letterSpacing. tracking-normal is an explicit 0em, not "unset" — Material's bodyLarge otherwise leaks letterSpacing = 0.5sp into every <Text>.