Shadow and Border
A border strokes the outline of a view, and a shadow draws a blurred copy of its shape behind it. Together with
CornerRadius and Clip they shape the box of a view. They are not views of their own but modifiers that every
styled view has: VStack, HStack, ZStack, Box, Text, Icon, Image, Button, TextField, List, ScrollView and
the others.
Use a border to separate a surface from its surroundings, or to mark a state such as a selection. Use a shadow to lift a surface above the content around it.

// A tile is a surface with a title and a caption. The modifiers that
// differ per tile are applied by the caller.
tile := func(title, caption string) ui.Stack {
return ui.VStack(
ui.Text(title),
ui.Text(caption).FontSize(12).Foreground(ui.ColorSecondaryLabel),
).Gap(4).Padding(16).Frame(160, 96).
Background(ui.ColorSurface).CornerRadius(12)
}
// A hairline one logical pixel wide in the separator color, like a card.
hairline := tile("Hairline", "Width 1, separator").
Border(ui.Border{Width: 1, Color: ui.ColorSeparator})
// A thicker border in the accent color. It lies inside the bounds, so
// the tile keeps its size.
accent := tile("Accent", "Width 2, accent").
Border(ui.Border{Width: 2, Color: ui.ColorAccent})
// A soft shadow below the tile. It does not change the layout.
soft := tile("Soft shadow", "Blur 16, offset 4").
Shadow(ui.Shadow{Blur: 16, OffsetY: 4, Color: ui.RGBA(0, 0, 0, 60)})
// A negative spread tucks the shadow under the tile, so only the
// offset side shows.
lifted := tile("Lifted", "Spread -6, offset 12").
Shadow(ui.Shadow{Blur: 20, Spread: -6, OffsetY: 12, Color: ui.RGBA(0, 0, 0, 90)})
// A picture clipped to its rounded corners, with a border drawn on top.
// The caption is a second layer of a ZStack.
picture := ui.ZStack(
ui.Image(pictures[0]).Fit(ui.FitCover).Frame(160, 96).
CornerRadius(12).Clip(true).
Border(ui.Border{Width: 2, Color: ui.ColorSurface}).
Shadow(ui.Shadow{Blur: 16, OffsetY: 4, Color: ui.RGBA(0, 0, 0, 60)}),
ui.VStack(
ui.Text("Clipped picture").Foreground(ui.RGB(255, 255, 255)),
ui.Text("Radius 12, border 2").FontSize(12).Foreground(ui.RGBA(255, 255, 255, 210)),
).Gap(4).Padding(16),
)
return ui.HStack(hairline, accent, soft, lifted, picture).
Gap(24).Align(geom.Alignment{Y: 0.5})Full demo on GitHub ↗
· run it with go run ./docs/gallery -demo shadow-and-border
Usage
Border
Border takes a ui.Border with a Width in logical pixels and a Color. The stroke lies inside the bounds of
the view, so a border never changes the layout: a tile keeps its size whether its border is 1 or 4 pixels wide. A
border with a width of zero or a fully transparent color draws nothing.
ui.VStack(ui.Text("Selected")).Padding(16).
Background(ui.ColorSurface).
CornerRadius(12).
Border(ui.Border{Width: 2, Color: ui.ColorAccent})Use semantic colors such as ui.ColorSeparator or ui.ColorAccent. They are resolved against the current theme
each time the view is built, so the border follows a switch between light and dark.
Shadow
Shadow takes a ui.Shadow. Blur is the blur diameter, the same value CSS and design tools use: the Gaussian
has a standard deviation of Blur/2, and the visible falloff reaches about one and a half times Blur past the
shape. A Blur of zero gives a hard edge. OffsetX and OffsetY move the shadow, positive right and down.
Spread grows the shape before it is blurred; a negative Spread shrinks it and tucks the shadow under the view,
so only the offset side shows.
card := ui.VStack(ui.Text("Lifted")).Padding(16).
Background(ui.ColorSurface).
CornerRadius(12).
Shadow(ui.Shadow{Blur: 20, Spread: -6, OffsetY: 12, Color: ui.RGBA(0, 0, 0, 90)})A shadow extends the painted area, but neither the layout size nor the hit area. It never pushes a sibling away and
never makes the gap around a view clickable. Leave room for it: a parent that clips, such as a ScrollView, cuts
the shadow off at its edge like anything else, so give the content some padding.
The shadow is the shape of the view’s bounds, drawn whether or not the view has a background. Give a shadowed view an opaque background, or the shadow shows through it. On a dark background a black shadow has little contrast, so in the dark theme a border or a lighter surface often separates better.
A Blur that is negative or not finite, and a Spread or offset that is not finite, panic when the modifier is
applied, so the mistake shows up at the call site and not as an empty window.
Corner radius
CornerRadius rounds the background, the border and the shadow together. The shadow’s corners grow with a positive
Spread and shrink with a negative one, down to square corners. The radius does not cut the content; that is what
Clip is for.
Clip
Content clipping is off by default, so a child may paint past the bounds of its parent. Clip(true) confines the
children to the bounds of the view, for painting and for input alike: a clipped part of a child neither shows nor
receives taps. A few views always clip; see the end of this section.
The clip of a container is the bounding rectangle, not the rounded shape. A child that fills a rounded stack covers
its rounded corners even with Clip(true). Pictures are the exception: Image with a CornerRadius and
Clip(true) rounds the picture itself, as the last tile of the demo does.
ui.Image(src).Fit(ui.FitCover).Frame(160, 96).
CornerRadius(12).
Clip(true)The clip surrounds the content, not the view’s own drawing. Its shadow still extends past the bounds, and its
border is drawn on top of the clipped content. ScrollView, VirtualList, ImageGallery, TextField and
OnScreenKeyboard always clip; Clip(false) on them panics.
Drawing order
A view draws in a fixed order: shadow, background, content, border. The order does not depend on the order in which you call the modifiers. The border is drawn last, so it lies on top of the children, which is how the demo frames a picture with a border in the surface color. Calling a modifier again replaces its value; the last call wins.
API
ui.Border and ui.Shadow are aliases of render.Border and render.Shadow. They are plain structs; you set their
fields directly.
Border
| Field | Type | Description |
|---|---|---|
Width | float32 | Stroke width in logical pixels, drawn inside the bounds. |
Color | Color | Stroke color. A transparent color draws nothing. |
Shadow
| Field | Type | Description |
|---|---|---|
Blur | float32 | Blur diameter in logical pixels; the standard deviation is Blur/2. Zero is a hard edge. |
Spread | float32 | Grows the shape before the blur; a negative value shrinks it. |
OffsetX | float32 | Horizontal offset, positive to the right. |
OffsetY | float32 | Vertical offset, positive downwards. |
Color | Color | Color at full coverage. The zero Shadow draws nothing. |
See render.Border and
render.Shadow on pkg.go.dev for the helper methods
IsVisible, Sigma and Extent.
Related
- Colors: the semantic colors to use for borders and backgrounds.
- Gradient and Glass: the other kinds of background.
- Card: a surface with a corner radius and a hairline border.
- Image: rounded, clipped pictures.
- Button: borders and corner radii per button state.
- Modifiers: how style modifiers work on every view.
- Theming: how semantic colors resolve in light and dark.