Styling
AliveJTUI provides a layered styling system: individual node methods for quick styling, a Style object for reusable styles, a Color enum/factory for colors, a Theme for application-wide semantic colors, and a StyleSheet for CSS-like selector-based styling.
Style
Style is an immutable value object. Build from Style.DEFAULT using with* methods:
// Single attribute
Style bold = Style.DEFAULT.withBold(true);
Style dim = Style.DEFAULT.withDim(true);
Style under = Style.DEFAULT.withUnderline(true);
// Compound style
Style fancy = Style.DEFAULT
.withForeground(Color.CYAN)
.withBackground(Color.BRIGHT_BLACK)
.withBold(true)
.withItalic(true);
Applying a Style to a Node
// Full style object
Text.of("hello").style(fancy);
// Shorthand methods (equivalent)
Text.of("hello")
.bold()
.italic()
.color(Color.CYAN)
.background(Color.BRIGHT_BLACK);
Shorthand vs Style object
For one-offs, the shorthand methods are cleaner. For reusable styles shared across many nodes, define a Style constant and apply it with .style(myStyle).
Color
Standard ANSI 16 Colors
// Dark variants
Color.BLACK, Color.RED, Color.GREEN, Color.YELLOW,
Color.BLUE, Color.MAGENTA, Color.CYAN, Color.WHITE
// Bright variants
Color.BRIGHT_BLACK, Color.BRIGHT_RED, Color.BRIGHT_GREEN,
Color.BRIGHT_YELLOW, Color.BRIGHT_BLUE, Color.BRIGHT_MAGENTA,
Color.BRIGHT_CYAN, Color.BRIGHT_WHITE
256-Color Palette
256-color indices follow the standard xterm-256color table.
True Color (24-bit RGB)
Color.rgb(255, 128, 0) // orange
Color.rgb(0, 200, 255) // sky blue
Color.rgb(180, 0, 180) // purple
Terminal support
True color (Color.rgb(...)) requires a terminal that supports 24-bit color (most modern terminals do). On terminals that don't, AliveJTUI falls back gracefully to the nearest ANSI color.
Theme
The theme system provides semantic color roles so your UI adapts consistently when the user switches between dark and light modes.
Switching Themes
Using Theme Colors in render()
@Override
public Node render() {
Theme t = AliveJTUI.getTheme();
return VBox.of(
Text.of("Title").style(t.primary()),
Text.of("Subtitle").style(t.muted()),
Text.of("All good").style(t.success()),
Text.of("Be careful").style(t.warning()),
Text.of("Something failed").style(t.error()),
Text.of("Focused item").style(t.focused())
);
}
Semantic Roles
| Method | Purpose |
|---|---|
t.foreground() |
Default text |
t.muted() |
Secondary / hint text |
t.primary() |
Primary highlight (headings, active items) |
t.secondary() |
Secondary highlight |
t.success() |
Success state (green tones) |
t.warning() |
Warning state (yellow tones) |
t.error() |
Error state (red tones) |
t.focused() |
Focus indicator |
Custom Theme
Theme myTheme = new Theme.BuiltinTheme(
Style.DEFAULT, // foreground
Style.DEFAULT.withDim(true), // muted
Style.DEFAULT.withForeground(Color.rgb(0, 200, 255))
.withBold(true), // primary
Style.DEFAULT.withForeground(Color.MAGENTA), // secondary
Style.DEFAULT.withForeground(Color.GREEN), // success
Style.DEFAULT.withForeground(Color.YELLOW), // warning
Style.DEFAULT.withForeground(Color.RED), // error
Style.DEFAULT.withForeground(Color.CYAN).withBold(true) // focused
);
AliveJTUI.setTheme(myTheme);
Theme at runtime
You can call AliveJTUI.setTheme() at any time — for example, in response to a keyboard shortcut or a settings toggle. The next render cycle picks up the new theme automatically.
StyleSheet (CSS-like selectors)
StyleSheet lets you define styles centrally and apply them to a node tree using selectors — similar to CSS.
Creating a StyleSheet
StyleSheet sheet = new StyleSheet()
.add(Selector.byId("title"), Style.DEFAULT.withForeground(Color.CYAN).withBold(true))
.add(Selector.byClass("muted"), Style.DEFAULT.withDim(true))
.add(Selector.byClass("danger"), Style.DEFAULT.withForeground(Color.RED).withBold(true))
.add(Selector.byType(ButtonNode.class), Style.DEFAULT.withForeground(Color.YELLOW));
Tagging Nodes
Text.of("My App").withId("title") // matches Selector.byId("title")
Text.of("Press ESC to quit").withClassName("muted") // matches Selector.byClass("muted")
Button.of("Delete", action) // matches Selector.byType(ButtonNode.class)
Applying to a Tree
@Override
public Node render() {
Node root = VBox.of(
Text.of("My App").withId("title"),
Text.of("Press ESC to quit").withClassName("muted"),
Button.of("[Delete]", this::deleteAction).withClassName("danger")
);
sheet.applyToTree(root); // walk the tree and apply matching styles
return root;
}
Selector Types
| Selector | Matches |
|---|---|
Selector.byId("name") |
Node where withId("name") was called |
Selector.byClass("name") |
Node where withClassName("name") was called |
Selector.byType(ButtonNode.class) |
All nodes of the given type |
Selector specificity
Styles are applied in declaration order. Later rules override earlier ones, similar to CSS cascade. For important overrides, just add your rule last.
Putting It All Together
public class StyledApp extends Component {
private static final StyleSheet SHEET = new StyleSheet()
.add(Selector.byId("header"), Style.DEFAULT.withForeground(Color.CYAN).withBold(true))
.add(Selector.byClass("hint"), Style.DEFAULT.withDim(true))
.add(Selector.byType(ButtonNode.class), Style.DEFAULT.withForeground(Color.YELLOW));
@Override
public Node render() {
Theme t = AliveJTUI.getTheme();
Node root = VBox.of(
Text.of(" My Application").withId("header"),
Divider.horizontal(),
Text.of(" Use arrow keys to navigate").withClassName("hint"),
HBox.of(
Button.of("[ OK ]", this::onOk),
Button.of("[ Cancel ]", this::onCancel)
)
);
SHEET.applyToTree(root);
return root;
}
}