Animated Borders: Living Frames in a Terminal UI Toolkit
August 12, 2026
A border in a terminal UI is usually a static thing. Four corners, four edges, drawn once and forgotten. Box-drawing characters sit there like a picture frame — functional, boring, dead.
jterm’s new AnimatedBorder system changes that. Borders can now sparkle, march, rotate, pulse, and scan. The login dialog in Phosphor BBS picks one at random for every new connection, so each user sees a different animated frame when they log in. The terminal isn’t a static page anymore — it’s alive, even down to the border.
The Architecture
Three pieces make this work: a strategy interface, a mutable context, and a timer-driven border widget.
AnimatedBorderEffect is a two-method strategy interface:
public interface AnimatedBorderEffect {
void update(long frame, BorderContext ctx);
String name();
}
Each animation frame, the AnimatedBorder widget’s timer calls update() with a monotonically increasing frame counter and a mutable BorderContext. The effect modifies whatever corners and edges it wants, and the border redraws using the updated characters. That’s the whole contract.
BorderContext is the mutable state passed to each effect. It holds:
- Four corners (TL, TR, BL, BR) with per-corner character and color overrides
- Per-position edge overrides for all four sides
- A global border color override
- A
resetToStyle()method that restores everything to the baseBorderStyledefaults
Effects that only touch corners (like SparkleCorners) leave edges untouched. Effects that animate edges (like MarchingAnts) leave corners alone. Effects that change color (like ColorPulse) set the global border color and let the renderer handle the rest. Each effect does one thing, and the context makes composition possible.
AnimatedBorder extends jterm’s Border widget and wires the timer:
public class AnimatedBorder extends Border {
private static final int DEFAULT_FPS = 10;
private final AnimatedBorderEffect effect;
private AnimationTimer timer;
public void start() {
if (timer != null && timer.isRunning()) return;
timer = new AnimationTimer(fps, this::onFrame);
timer.start();
}
public void stop() {
if (timer != null) timer.stop();
}
private void onFrame(long frame) {
this.frameCounter = frame;
ctx.resetToStyle();
effect.update(frame, ctx);
requestRefresh();
}
}
The timer fires at 10 FPS by default. Each tick resets the context to the base style, lets the effect modify it, and requests a screen refresh. The border’s drawComponent() method then renders using the context’s current characters and colors instead of the static style defaults.
The Five Effects
SparkleCorners cycles the four corner characters through a 16-step sequence of Unicode block elements (▘▝▖▗▌▐▀▄▄▀▐▌▗▖▝▘). Each corner is offset by 90 degrees — TL at phase 0, TR at phase 4, BR at phase 8, BL at phase 12 — so they sparkle in sequence around the frame. Edges stay still. The effect is subtle and hypnotic.
MarchingAnts is the classic Photoshop selection marquee. Dashes scroll around the border perimeter: top and bottom edges move right, left and right edges move down. The dash-to-gap ratio is configurable (default 3 dashes, 2 gaps), matching the original selection animation. Horizontal edges use - for dashes and · for gaps; vertical edges use | and ·. Corners stay fixed.
RotatingDashCorners spins the four corners through an 8-step sequence: ┌ ╱ ┐ ╲ ┘ ╱ └ ╲. Each corner is offset by 2 steps in the 8-step cycle, creating a pinwheel illusion. The diagonal characters (╱╲) between the corner characters sell the rotation — it looks like the frame is turning.
ColorPulse doesn’t change characters at all. Instead, it cycles the border’s foreground color through the ANSI palette: RED → GREEN → YELLOW → BLUE → MAGENTA → CYAN → WHITE, then back to RED. Each color is held for 10 frames (about 1 second at 10fps), so the transition feels gentle rather than strobing. The characters stay the same; only the color shifts.
ScanningLine sends a bright character (■ by default, in BRIGHT_WHITE) traveling clockwise around the border perimeter — top edge left to right, right edge top to bottom, bottom edge right to left, left edge bottom to top. One cell per tick. The rest of the border stays at the base style, so it looks like a scanner sweeping the frame. The speed is configurable (cells per frame) for faster or slower sweeps.
The Window API
The AnimatedBorderWindow class wraps any content in an AnimatedBorder and manages the animation lifecycle automatically. The window uses NO_DECORATIONS — the animated border is the decoration. A title can be rendered in the top border line. The setEffect() method switches effects at runtime and resets the frame counter, which is how the interactive demo works: press keys 1–5 to swap between effects live.
On the Login Screen
The Phosphor BBS login dialog now picks one of the five effects at random for each new connection:
var effect = AnimationFactory.randomBorderEffect();
var border = new AnimatedBorder(loginPanel, effect);
border.start();
Animation starts when the screen opens and stops on close, login, or disconnect. Every visitor sees a different animated frame. Some get sparkles. Some get marching ants. Some get a scanning line. It’s a small touch, but it sets the tone before the user has even typed their name.
Why It Matters
Terminal UI doesn’t have to be static. Every surface — backgrounds, transitions, sprites, and now borders — can be alive. The AnimatedBorder system proves that even the most boring part of a UI widget (the frame around it) can be expressive without being distracting.
Five effects. A clean strategy interface. A mutable context that makes it trivial to write new ones. And a login screen that’s different every time you connect.
The terminal is alive again — right down to the edges.
#BBS #terminal #jterm #opensource #animation