Documentation

Shaders and Colors

A shader as a background, written the way CSS writes one, and the color grammar every color prop shares.

paint()

<Box
  class="w-full h-full"
  background="paint(/shaders/sky.agsl, sunAlt: {sunAlt}, tint: 0.2 0.4 0.9)"
/>

This is CSS's background-image: paint(worklet), pointed at an AGSL file in static/. The path names the file the way $navigate("/x") names a route. Arguments are name: value; a value is a number, several numbers for a vector, or {expression}.

The shader

// static/shaders/sky.agsl
uniform float2 resolution;
uniform float sunAlt;
uniform float3 tint;

half4 main(float2 fragCoord) {
  float2 uv = fragCoord / resolution;
  return half4(mix(tint, float3(1.0), uv.y * sunAlt), 1.0);
}

The uniforms are declared in the shader and nowhere else. The compiler reads the shader file and checks every call against it. resolution, a float2, is supplied: the element's measured size, which a call site cannot know.

Build errors

Each of these is a build error at the call site, rather than an exception at draw time on a device:

  • a uniform the call sets that the shader does not declare; the message lists the ones it does
  • a uniform the shader declares that the call leaves out (except resolution)
  • the wrong number of components for the uniform's type
  • no shader at the path; the message lists the .agsl files that exist
  • a path that is not an .agsl file
  • fwidth, texture, discard or gl_FragCoord in the shader, reported at the shader's own line and column. It is AGSL, not GLSL
  • min_sdk below 33

Needs min_sdk = 33

# whitehall.toml
[android]
min_sdk = 33

AGSL arrived with Android 13. Below it there is nothing to run the shader, so paint() with a lower min_sdk is a build error that says so.

Uniforms are set in the draw phase. A shader driven by a gesture or an animated value costs one draw per frame and no recomposition.

Colors and gradients

Every color prop takes the same grammar: a theme token (primary), a hex, a Tailwind palette name (yellow-500), or color(display-p3 1 0.2 0), each with /N for opacity as a percentage.

<Text color="primary" />                             // a theme token
<Text color="#FF5722/50" />                          // hex, 50% opacity
<Box background="v-gradient(#ff0000, #0000ff)" />
<Image src={url} overlay="v-gradient(transparent 60%, #000)" />

v-gradient and h-gradient are vertical and horizontal. CSS's linear-gradient (with an optional leading 90deg, where 0deg points up and angles turn clockwise), radial-gradient and conic-gradient are also accepted. A stop may carry a position: transparent 60%.

overlay on an <Image> draws a gradient over the picture, for text contrast.

Try it

paint() needs a file in static/, so the editor below covers the color grammar only.

Colors.wh
<Column class="gap-8">
  <Box background="v-gradient(#ff0000, #0000ff)" class="w-full h-80" />
  <Box background="h-gradient(primary, #FF5722/50)" class="w-full h-80" />
  <Box background="v-gradient(transparent 60%, #000)" class="w-full h-80" />
</Column>
Kotlin waiting
Scroll into view to compile.

v-gradient runs top to bottom and h-gradient left to right. A stop may carry a position, and every stop takes the same color grammar as a color prop.

See Also

  • Images — overlay and fit
  • Theming — where the color tokens come from
  • <Path> — stroke and fill take this grammar