Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Borders

Live Demo

Open the demo in a separate tab.

Beverly’s Surface renderer paints borders around a surface’s shape. A border combines widths in pixels with a Paint, which can be a solid color or a gradient.

BeverlyPlugin includes the rendering plugin required for these examples. For shared design values rather than rendering APIs, see Border Tokens.

Uniform Border

Use uniform_border to give every side the same width and paint:

#![allow(unused)]
fn main() {
use beverly::rendering::{Paint, Surface};
use bevy::prelude::*;

fn add_bordered_surface(parent: &mut ChildSpawnerCommands) {
    parent.spawn((
        Node {
            width: Val::Px(240.0),
            height: Val::Px(120.0),
            padding: UiRect::all(Val::Px(20.0)),
            ..default()
        },
        Surface::rounded_rect_fill(8.0, Paint::solid(Color::WHITE))
            .uniform_border(
                4.0,
                Paint::solid(Color::srgb(0.12, 0.68, 0.48)),
            ),
    ));
}
}

The surface’s shape determines its corners; border thickness does not set the corner radius. A width of 0.0 makes the border invisible.

The equivalent explicit configuration is .border(Border::new(width, paint)).

Individual Sides

Use Border::per_side with BorderWidths::sides. The argument order is top, right, bottom, left.

#![allow(unused)]
fn main() {
use beverly::rendering::{Border, BorderWidths, Paint, Surface};
use bevy::prelude::*;

let underline = Surface::rounded_rect_fill(0.0, Paint::solid(Color::WHITE))
    .border(Border::per_side(
        BorderWidths::sides(0.0, 0.0, 4.0, 0.0),
        Paint::solid(Color::srgb(0.5, 0.5, 0.5)),
    ));

let asymmetric = Surface::rounded_rect_fill(8.0, Paint::solid(Color::WHITE))
    .border(Border::per_side(
        BorderWidths::sides(2.0, 4.0, 6.0, 8.0),
        Paint::solid(Color::srgb(0.12, 0.55, 0.90)),
    ));
}

Other width constructors include:

ConstructorResult
BorderWidths::all(width)Same width on every side
BorderWidths::symmetric(vertical, horizontal)Top/bottom use vertical; left/right use horizontal
BorderWidths::horizontal_vertical(horizontal, vertical)Same pairing, with reversed argument order
BorderWidths::ZERONo border on any side

The uniform and symmetric constructors clamp negative widths to zero. sides stores its arguments directly; use nonnegative widths or call .clamped_non_negative().

Gradient Border

Border paint is independent of fill paint. A surface can have a solid background and a gradient outline:

#![allow(unused)]
fn main() {
use beverly::rendering::{GradientStop, LinearGradient, Paint, Surface};
use bevy::prelude::*;

let surface = Surface::rounded_rect_fill(8.0, Paint::solid(Color::WHITE))
    .uniform_border(
        4.0,
        Paint::linear(LinearGradient::horizontal(vec![
            GradientStop::new(0.0, Color::srgb(0.12, 0.68, 0.48)),
            GradientStop::new(1.0, Color::srgb(0.94, 0.30, 0.34)),
        ])),
    );
}

This paints a continuous gradient around the outline. It does not assign a separate color to each side. See Gradients for additional paint types.

For a translucent solid border, use Paint::solid(color.with_alpha(opacity)). Border opacity does not change the surface fill or its child content.

Layout and Theme

Surface::border describes the painted border. Bevy’s Node::border describes layout border widths. Configure layout spacing explicitly when content needs room beside a thick outline; the demo uses padding to keep its labels clear of the painted edges.

Do not add a second visible BorderColor unless you intend to draw a separate Bevy border as well.

Explicit border colors remain unchanged when the theme switches. To follow the theme, read ThemeResource and update the border paint:

#![allow(unused)]
fn main() {
use beverly::rendering::{Paint, Surface};
use beverly::theme::ThemeResource;
use bevy::prelude::*;

#[derive(Component)]
struct ThemedOutline;

fn update_outlines(
    theme: Res<ThemeResource>,
    mut surfaces: Query<&mut Surface, With<ThemedOutline>>,
) {
    let paint = Paint::solid(theme.current.colors.border);
    for mut surface in &mut surfaces {
        if let Some(border) = surface.border.as_mut() {
            if border.paint != paint {
                border.paint = paint.clone();
            }
        }
    }
}
}

Register this system in Update and attach ThemedOutline to the relevant surfaces. Use surface.clear_border() to remove a border entirely. Border setters replace the existing border rather than adding another outline.

Demo

Run the interactive border demo from the repository root:

cargo run --example borders
cargo run --example borders -- dark

The demo includes four color swatches and a 0–16 pixel thickness slider. Its previews show uniform, bottom-only, asymmetric, and gradient borders. The asymmetric widths are half, one, one-and-a-half, and twice the slider value, in top/right/bottom/left order.

The gradient runs from the selected color to coral. Preview fills follow the active theme, and the top-right button switches between light and dark mode.