Colors, Fonts & Text
After placing shapes on the scene graph, the next skill every JavaFX developer must master is styling text. Typography and color are not cosmetic afterthoughts — they carry hierarchy, brand, and readability. JavaFX gives you a rich, immutable value-type system for both, and understanding how that system works will save you from common pitfalls around object reuse and CSS overrides.
The Color Class
javafx.scene.paint.Color is an immutable value type. You never modify a Color instance — you create a new one instead. JavaFX ships with about 150 named constants (Color.DODGERBLUE, Color.CORAL, etc.), but in real applications you will most often use one of three factory methods.
RGB (0.0–1.0 doubles):
Color c1 = Color.color(0.2, 0.6, 1.0); // r, g, b — each 0.0 to 1.0
Color c2 = Color.color(0.2, 0.6, 1.0, 0.8); // fourth arg is opacity (alpha)
RGB (0–255 integers):
Color c3 = Color.rgb(51, 153, 255); // familiar 8-bit values
Color c4 = Color.rgb(51, 153, 255, 0.8); // with opacity
Hex string (CSS-style):
Color c5 = Color.web("#3399ff"); // standard 6-digit hex
Color c6 = Color.web("#3399ff", 0.8); // hex + opacity
Color c7 = Color.web("rgba(51,153,255,0.8)"); // also accepts CSS rgba()
Use Color.web() when working from a design spec. Designers hand off hex codes. Color.web() accepts them directly, removing the manual conversion step and making the code self-documenting.
Applying Color to a Text Node
In JavaFX, the node class for displaying text is javafx.scene.text.Text. Its fill (foreground color) is set with setFill(Paint). Color implements the Paint interface, so you pass a Color directly.
import javafx.scene.text.Text;
import javafx.scene.paint.Color;
Text heading = new Text("Hello, JavaFX");
heading.setFill(Color.web("#1a1a2e"));
heading.setStroke(Color.web("#3399ff")); // optional outline stroke
heading.setStrokeWidth(0.5);
For background colors and borders on layout containers (VBox, HBox, etc.) you set a style string or use an inline CSS property, since those nodes do not have a setFill method directly:
VBox root = new VBox(10);
root.setStyle("-fx-background-color: #f0f4ff;");
The Font Class
javafx.scene.text.Font is also immutable. Every Font object is a snapshot of family, weight, posture, and size. You construct fonts through static factory methods.
Default font at a specific size:
Font small = Font.font(12);
Font normal = Font.font(14);
Font large = Font.font(24);
Named family, specific size:
Font sansSerif = Font.font("Arial", 16);
Font mono = Font.font("Courier New", 14);
Named family with weight and/or posture:
import javafx.scene.text.FontWeight;
import javafx.scene.text.FontPosture;
Font bold = Font.font("Segoe UI", FontWeight.BOLD, 18);
Font italic = Font.font("Georgia", FontPosture.ITALIC, 16);
Font boldItalic = Font.font("Georgia", FontWeight.BOLD, FontPosture.ITALIC, 16);
Font availability is platform-dependent. Font.font("Segoe UI", ...) works on Windows but will silently fall back to a system default on macOS or Linux. For cross-platform apps, load a bundled font file or choose a universally available family like Arial or Courier New.
Loading a Custom Font from Resources
Bundling a font in your JAR and loading it at runtime is the correct approach for any application that must look the same on every OS.
// Place the file at: src/main/resources/fonts/Roboto-Regular.ttf
Font roboto = Font.loadFont(
getClass().getResourceAsStream("/fonts/Roboto-Regular.ttf"),
16 // requested size
);
if (roboto == null) {
// File not found — fall back gracefully
roboto = Font.font("Arial", 16);
}
After loading, JavaFX registers the font family name from inside the TTF/OTF metadata, so you can also reference it later via Font.font("Roboto", ...) in the same session.
Applying Fonts to Text Nodes
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.paint.Color;
Text title = new Text("JavaFX Typography");
title.setFont(Font.font("Arial", FontWeight.BOLD, 28));
title.setFill(Color.web("#1a1a2e"));
Text body = new Text("Fonts and colors give text hierarchy and personality.");
body.setFont(Font.font("Arial", 14));
body.setFill(Color.web("#444444"));
Text Wrapping and Alignment
A plain Text node renders on one line by default. Set wrappingWidth to enable automatic line breaks:
Text paragraph = new Text(
"JavaFX provides a rich text API with multi-line support, "
+ "custom fonts, and full Unicode coverage."
);
paragraph.setFont(Font.font("Arial", 14));
paragraph.setFill(Color.web("#333333"));
paragraph.setWrappingWidth(300); // wrap at 300 logical pixels
For richer text blocks inside layouts, javafx.scene.control.Label is often more convenient than a raw Text node — it integrates with the layout system and supports setAlignment() and setWrapText(true).
import javafx.scene.control.Label;
import javafx.geometry.Pos;
Label label = new Label("Welcome to the app");
label.setFont(Font.font("Arial", FontWeight.BOLD, 18));
label.setTextFill(Color.web("#1a1a2e")); // Note: Label uses setTextFill, not setFill
label.setAlignment(Pos.CENTER);
label.setWrapText(true);
Label.setTextFill() vs Text.setFill() — the methods have different names! Text inherits from Shape and uses setFill(Paint). Label inherits from Control and uses setTextFill(Paint). Calling the wrong one compiles fine but does nothing visible.
Putting It All Together — A Styled Card
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;
public class StyledCardApp extends Application {
@Override
public void start(Stage primaryStage) {
Label title = new Label("JavaFX Typography Demo");
title.setFont(Font.font("Arial", FontWeight.BOLD, 22));
title.setTextFill(Color.web("#1a1a2e"));
Label subtitle = new Label("Colors, Fonts & Text — Lesson 7");
subtitle.setFont(Font.font("Arial", 14));
subtitle.setTextFill(Color.web("#6c757d"));
Label body = new Label(
"JavaFX\'s Font and Color classes are immutable value types. "
+ "Each call creates a new object rather than mutating state, "
+ "making them safe to share across threads."
);
body.setFont(Font.font("Arial", 13));
body.setTextFill(Color.web("#343a40"));
body.setWrapText(true);
body.setMaxWidth(360);
VBox card = new VBox(10, title, subtitle, body);
card.setPadding(new Insets(24));
card.setStyle("-fx-background-color: #ffffff; -fx-border-color: #dee2e6; "
+ "-fx-border-radius: 8; -fx-background-radius: 8;");
VBox root = new VBox(card);
root.setPadding(new Insets(32));
root.setStyle("-fx-background-color: #f8f9fa;");
primaryStage.setScene(new Scene(root, 440, 240));
primaryStage.setTitle("Styled Card");
primaryStage.show();
}
public static void main(String[] args) { launch(args); }
}
Summary
Color and Font are immutable snapshot objects — you create them, you do not mutate them. Use Color.web() for designer hex codes and load custom fonts at startup to guarantee consistent rendering. Apply color with setFill() on Text nodes and setTextFill() on Label controls — they are different APIs on different class hierarchies. In the next lesson you will add interactivity by handling your first user event.