Utility-first UI framework for JavaFX, inspired by Tailwind CSS.
TailwindFX brings Tailwind CSS's utility-first approach to JavaFX. Instead of writing boilerplate style code, you compose styles from a comprehensive set of pre-built utility classes β and where CSS falls short, TailwindFX provides equivalent Java APIs.
// Before β JavaFX vanilla
btn.setStyle(
"-fx-background-color: #3b82f6; " +
"-fx-text-fill: white; " +
"-fx-background-radius: 8px; " +
"-fx-padding: 8px 16px;"
);
// Hover animation with vanilla JavaFX
btn.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> {
Timeline tl = new Timeline(
new KeyFrame(Duration.ZERO,
new KeyValue(btn.scaleXProperty(), btn.getScaleX()),
new KeyValue(btn.scaleYProperty(), btn.getScaleY())),
new KeyFrame(Duration.millis(150),
new KeyValue(btn.scaleXProperty(),1.05),
new KeyValue(btn.scaleYProperty(),1.05))
);
tl.play();
});
// ... same pattern for MOUSE_EXITED scaling back to 1.0
// With TailwindFX
TailwindFX.apply(btn, "btn-primary", "rounded-lg", "px-4", "py-2");
FxAnimation.onHoverScale(btn, 1.05);| Feature | Description |
|---|---|
| 1,400+ CSS utilities | Layout, typography, colors, shadows, effects, transforms |
| JIT compiler | bg-blue-500/80, p-[13px], drop-shadow-[#3b82f6] arbitrary values |
| FxFlexPane | Real flexbox: direction, wrap, justify-content (6), align-items (4), gap, flex-grow/shrink/basis |
| FxGridPane | Grid-template-areas, masonry, auto-flow |
| FxDataTable | Sortable, filterable, paginated TableView wrapper |
| ResponsiveNode | Per-node breakpoint rules driven by Scene.widthProperty() |
| Themes | Dark/light/blue/green/purple/rose/slate + scoped subtree themes |
| Animations | fadeIn/Out, slideUp/Down/Left/Right, shake, bounce, pulse, spin + hover effects |
| Tailwind v4.1 | text-shadow, drop-shadow-[color], SVG fill/stroke, 3D transforms, clip/mask |
| Glassmorphism | TailwindFX.glass(), backdropBlur(), .glass CSS class |
| Neumorphism | TailwindFX.neumorph(), .neumorph CSS class |
| ComponentFactory | Cards, badges, modals, drawers, tooltips |
| Metrics + alerts | Cache hit ratio, conflict rate, compile time alerts |
| Performance | StyleDiff (skip redundant applies), batch apply, LRU cache |
<dependency>
<groupId>io.github.tailwindfx</groupId>
<artifactId>tailwindfx</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>public class MyApp extends Application {
@Override
public void start(Stage stage) {
StackPane root = new StackPane();
Scene scene = new Scene(root, 900, 600);
// 1. Install (loads CSS + wires breakpoints)
TailwindFX.install(scene, stage);
// 2. Build UI with utilities
VBox card = new VBox(12);
TailwindFX.apply(card, "card", "w-80");
Label title = new Label("Hello TailwindFX");
TailwindFX.apply(title, "text-2xl", "font-bold", "text-blue-600");
Button btn = new Button("Get Started");
TailwindFX.apply(btn, "btn-primary", "rounded-lg");
FxAnimation.onHoverScale(btn, 1.05);
card.getChildren().addAll(title, btn);
root.getChildren().add(card);
stage.setScene(scene);
stage.show();
}
}// Single or multiple classes:
TailwindFX.apply(node, "p-4", "bg-white", "rounded-lg", "shadow-md");
// JIT β arbitrary values compiled at runtime:
TailwindFX.apply(node, "bg-blue-500/80", "p-[13px]", "drop-shadow-[#3b82f6]");
// No conflict resolution (accumulate intentionally):
TailwindFX.applyRaw(node, "w-4", "w-8"); // both stay// Per-node rules (uses Scene.widthProperty()):
TailwindFX.responsive(sidebar)
.base("w-64", "flex-col")
.sm("w-full")
.md("w-48")
.onBreakpoint(bp -> System.out.println("Breakpoint: " + bp))
.install(scene);
// Scene-level breakpoints:
BreakpointManager bpm = TailwindFX.responsive(stage);
bpm.onBreakpoint(BreakpointManager.Breakpoint.MD,
() -> container.setDirection(FxFlexPane.Direction.ROW));FxFlexPane flex = TailwindFX.flexRow()
.wrap(true)
.justify(FxFlexPane.Justify.BETWEEN)
.align(FxFlexPane.Align.CENTER)
.gap(16);
FxFlexPane.setGrow(mainContent, 1); // flex-grow: 1
FxFlexPane.setShrink(sidebar, 0); // flex-shrink: 0
FxFlexPane.setBasis(child, 0); // flex-basis: 0
FxFlexPane.setAlignSelf(btn, FxFlexPane.Align.END); // align-self: end
FxFlexPane.setOrder(header, -1); // order: -1 (first)
// Animated direction change:
flex.setDirectionAnimated(FxFlexPane.Direction.COL, 200);FxGridPane page = FxGridPane.create()
.areas(
"header header",
"sidebar main",
"footer footer"
)
.gap(12).build();
page.placeIn(headerRegion, "header");
page.placeIn(sidebarRegion, "sidebar");
page.placeIn(mainContent, "main");
page.placeIn(footerRegion, "footer");
// Masonry layout:
FxGridPane pins = FxGridPane.create().masonry(3).gap(12).build();FxDataTable<User> table = TailwindFX.dataTable(User.class)
.column("Name", User::name)
.column("Email", User::email)
.column("Age", u -> String.valueOf(u.age()))
.searchable(true)
.pageSize(25)
.style("table-striped", "table-hover")
.build();
table.setItems(userList);
table.setFilter(u -> u.dept().equals("Engineering"));
root.getChildren().add(table.container());// Global theme:
TailwindFX.theme(scene).dark().apply();
TailwindFX.theme(scene).preset("blue").apply();
TailwindFX.saveTheme(scene, "myapp.theme");
TailwindFX.loadTheme(scene, "myapp.theme");
// Scoped theme (subtree only):
TailwindFX.scope(panel).preset("rose").apply();
TailwindFX.inheritScope(triggerButton, modal); // modal inherits trigger's scope
TailwindFX.refreshScope(panel); // after reparentingFxAnimation.fadeIn(node, 300).play();
FxAnimation.slideUp(node).play();
FxAnimation.shake(button).play(); // validation error
FxAnimation.spin(loadingIcon).loop().play();
FxAnimation.onHoverScale(btn, 1.05); // permanent hover scale
FxAnimation.onHoverLift(btn); // hover lift (-4px)
FxAnimation.onHoverDim(btn, 0.8); // hover dim
FxAnimation.removeHoverEffects(btn); // clean up all hover effects
// Chain / parallel:
FxAnimation.chain(fadeIn, slideUp).play();
FxAnimation.parallel(pulse, bounce).play();
// Reduced motion:
TailwindFX.setReducedMotion(true);
TailwindFX.playIfMotionOk(animation); // plays or jumps to end state// Text shadow:
TailwindFX.textShadowMd(heading);
TailwindFX.textShadow(label, "#3b82f6", 6, 0, 2); // colored
// Colored drop shadow:
TailwindFX.dropShadowBlue(card);
TailwindFX.dropShadow(card, "#22c55e", 0.4, 12, 0, 4);
// Clip/mask:
TailwindFX.clipCircle(avatar);
TailwindFX.clipRounded(imageView, 12);
// 3D transforms:
TailwindFX.rotateX(panel, 15);
TailwindFX.rotateY(card, 30);
TailwindFX.translateZ(tooltip, 50);
// Glass / neumorph:
TailwindFX.glass(overlayPane);
TailwindFX.backdropBlurMd(overlayPane);
TailwindFX.neumorph(button);
// SVG:
TailwindFX.fill(svgPath, "#3b82f6");
TailwindFX.stroke(svgPath, "#000000");TailwindFX.metrics()
.setEnabled(true)
.onAlert((metric, value, threshold) ->
System.err.printf("Alert: %s=%.2f%n", metric, value))
.alertOnLowCacheHitRatio(0.70)
.alertOnHighConflictRate(0.30)
.alertOnSlowCompile(0.5); // ms
TailwindFX.metrics().print(); // formatted report// Batch apply (1 CSS pass for many nodes):
TailwindFX.batch(() ->
cards.forEach(c -> TailwindFX.apply(c, "card", "shadow-md")));
// Configure auto-batch threshold:
TailwindFX.configure().autoBatch(20);
// Cleanup on node removal:
TailwindFX.cleanupNode(removedNode); // explicit
TailwindFX.autoCleanup(cellNode); // auto on scene removalMIT β see LICENSE for details.
We welcome contributions from the community! Here's how you can help:
- Check existing issues - Look for Good First Issues to get started
- Read our guides - See CONTRIBUTING.md and CODE_OF_CONDUCT.md
- Fork and submit PRs - Create a branch from
develop, make your changes, and submit a pull request - Report bugs - Use our bug report template
- Suggest features - Use our feature request template
- π Issues for Contributors
- π Report a Bug
- π‘ Request a Feature
- π― Good First Issues