HTML DSL
JWeb's HTML DSL provides Java methods for all HTML elements. Elements are composable, nestable, and fully type-checked at compile time.
Import Statement
import static com.osmig.Jweb.framework.elements.El.*;This single import gives you access to all HTML elements, attributes, and helpers.
Basic Pattern
Every HTML element has a corresponding Java method. Pass children as arguments to create nested structures.
// Simple element with text
h1("Hello World")
// Nested elements
div(
h1("Title"),
p("Content here")
)
// With attributes
div(attrs().id("main").class_("container"),
h1("Title"),
p("Content")
)
// With inline styles (lambda syntax)
div(attrs()
.class_("card")
.style(s -> s
.padding(rem(1))
.backgroundColor(white)
),
p("Styled content")
)Text Elements
Create headings, paragraphs, and inline text.
// Headings h1-h6
h1("Main Title")
h2("Section Title")
h3("Subsection")
h4("Sub-subsection")
h5("Minor heading")
h6("Smallest heading")
// Paragraphs and text
p("A paragraph of text")
span("Inline text")
text("Raw text node")
// Text formatting
strong("Bold/important text")
em("Emphasized/italic text")
small("Fine print")
code("Inline code")
pre("Preformatted text")Links
Create anchor links with href shorthand or full attributes.
// Simple link (href shorthand)
a("/home", "Home")
a("/about", "About Us")
// Link with attributes
a(attrs().href("/contact").class_("nav-link"), "Contact")
// External link (opens in new tab securely)
a(attrs().href("https://example.com").targetBlank(), "External Site")
// Adds: target="_blank" rel="noopener noreferrer"
// Link with multiple children
a(attrs().href("/profile"),
img("/avatar.png", "Avatar"),
span("View Profile")
)Semantic Structure
Use semantic elements for accessible, well-structured pages.
// Page structure
body(
header(
h1("Site Title"),
nav(
a("/", "Home"),
a("/about", "About"),
a("/contact", "Contact")
)
),
main(
article(
h2("Article Title"),
p("Article content...")
),
aside(
h3("Related Links"),
ul(li(a("/related", "Related Item")))
)
),
footer(
p("© 2026 My Company"),
address(a("mailto:email@example.com", "Contact Us"))
)
)
// Sectioning
section(
h2("Features"),
p("Our product features...")
)
// Search section (HTML5.2)
search(
form(attrs().action("/search"),
input(attrs().type("search").name("q")),
button("Search")
)
)Document Elements
Build complete HTML documents with head and body.
html(
head(
title("My Page"),
meta(attrs().charset("UTF-8")),
meta(attrs().name("viewport").content("width=device-width, initial-scale=1")),
css("/styles/main.css"),
icon("/favicon.ico")
),
body(
h1("Welcome"),
p("Page content")
)
)
// Helper methods
css("/styles.css") // Stylesheet link
icon("/favicon.ico") // Favicon
script("/app.js") // External script
inlineScript("console.log('Hi');") // Inline JSContainer Elements
Structural elements for organizing content.
// Basic containers
div(child1, child2, child3)
section(header, content, footer)
article(title, body)
aside(sidebar)
// Semantic structure
header(nav, logo)
main(content)
footer(links, copyright)
nav(menuItems)Nesting Elements
Elements can be nested to any depth.
div(
header(
h1("Welcome"),
nav(
a(attrs().href("/"), text("Home")),
a(attrs().href("/about"), text("About"))
)
),
main(
article(
h2("Article Title"),
p("Article content here...")
)
),
footer(p("Copyright 2025"))
)Lists
Unordered, ordered, and definition lists.
// Unordered list
ul(
li("First item"),
li("Second item"),
li("Third item")
)
// Ordered list
ol(
li("Step one"),
li("Step two"),
li("Step three")
)
// Definition list
dl(
dt("Term"),
dd("Definition of the term"),
dt("Another term"),
dd("Its definition")
)Dynamic Lists
Use each() to render lists from collections.
List<String> items = List.of("Apple", "Banana", "Cherry");
ul(each(items, item -> li(item)))
// With index
ul(eachWithIndex(items, (item, i) ->
li((i + 1) + ". " + item)
))
// Complex list items
List<User> users = userService.findAll();
ul(each(users, user ->
li(
strong(user.getName()),
text(" - "),
span(user.getEmail())
)
))Tables
Create data tables with headers, body, and footer.
table(
thead(
tr(th("Name"), th("Email"), th("Role"))
),
tbody(
tr(td("John"), td("john@test.com"), td("Admin")),
tr(td("Jane"), td("jane@test.com"), td("User"))
),
tfoot(
tr(td(attrs().colspan("3"), text("2 users total")))
)
)Dynamic Tables
Render table rows from data collections.
List<User> users = userService.findAll();
table(
thead(tr(th("ID"), th("Name"), th("Email"), th("Actions"))),
tbody(each(users, user -> tr(
td(String.valueOf(user.getId())),
td(user.getName()),
td(user.getEmail()),
td(
button(attrs().class_("btn-edit"), text("Edit")),
button(attrs().class_("btn-delete"), text("Delete"))
)
)))
)Sortable Table Headers
th(attrs()
.class_("sortable")
.data("sort", "name")
.onClick("sortTable('name')"),
text("Name "), span(attrs().class_("sort-icon")))Images
Display images with required alt text for accessibility.
// Basic image
img(attrs().src("/images/logo.png").alt("Company Logo"))
// Responsive image
img(attrs()
.src("/images/hero.jpg")
.alt("Hero image")
.class_("responsive")
.loading("lazy"))
// Figure with caption
figure(
img(attrs().src("/images/chart.png").alt("Sales chart")),
figcaption("Q4 2024 Sales Performance")
)Links
Create anchor links for navigation.
// Internal link
a(attrs().href("/about"), text("About Us"))
// External link (opens in new tab)
a(attrs()
.href("https://github.com")
.target("_blank")
.rel("noopener noreferrer"),
text("GitHub"))
// Download link
a(attrs()
.href("/files/report.pdf")
.download("report.pdf"),
text("Download Report"))
// Email link
a(attrs().href("mailto:contact@example.com"), text("Email Us"))Form Elements
Build forms with type-safe input elements and attributes.
// Basic form
form(attrs().action("/submit").method("POST"),
label(attrs().for_("email"), "Email:"),
input(attrs()
.type("email")
.name("email")
.id("email")
.placeholder("you@example.com")
.required()
),
button(attrs().type("submit"), "Submit")
)Input Types
All HTML5 input types are supported.
// Text inputs
input(attrs().type("text").name("username"))
input(attrs().type("password").name("pwd"))
input(attrs().type("email").name("email"))
input(attrs().type("tel").name("phone"))
input(attrs().type("url").name("website"))
input(attrs().type("search").name("q"))
// Number inputs
input(attrs().type("number").name("qty").min(1).max(100))
input(attrs().type("range").name("volume").min(0).max(100))
// Date/time inputs
input(attrs().type("date").name("birthday"))
input(attrs().type("time").name("appointment"))
input(attrs().type("datetime-local").name("meeting"))
input(attrs().type("month").name("expiry"))
input(attrs().type("week").name("week"))
// Selection inputs
input(attrs().type("checkbox").name("agree").value("yes"))
input(attrs().type("radio").name("color").value("red"))
// File input
input(attrs().type("file").name("avatar").accept("image/*"))
// Hidden input
input(attrs().type("hidden").name("csrf").value(token))
// Color picker
input(attrs().type("color").name("theme").value("#6366f1"))Textarea
Multi-line text input.
// Basic textarea
textarea(attrs().name("message").rows(5).cols(40))
// With default content
textarea(attrs().name("bio").placeholder("Tell us about yourself"),
"Default text here"
)
// With validation
textarea(attrs()
.name("comment")
.required()
.minlength(10)
.maxlength(500)
)Select & Options
Dropdown selects with options.
// Basic select
select(attrs().name("country"),
option("", "Select a country"),
option("us", "United States"),
option("uk", "United Kingdom"),
option("ca", "Canada")
)
// With selected option
select(attrs().name("size"),
option("sm", "Small"),
option(attrs().value("md").selected(), "Medium"),
option("lg", "Large")
)
// Multiple select
select(attrs().name("colors").multiple(),
option("red", "Red"),
option("green", "Green"),
option("blue", "Blue")
)
// Optgroup
select(attrs().name("car"),
optgroup(attrs().label("Swedish Cars"),
option("volvo", "Volvo"),
option("saab", "Saab")
),
optgroup(attrs().label("German Cars"),
option("mercedes", "Mercedes"),
option("audi", "Audi")
)
)Buttons
Different button types for various actions.
// Submit button (default)
button("Submit")
button(attrs().type("submit"), "Save")
// Reset button
button(attrs().type("reset"), "Clear Form")
// Regular button (for JS actions)
button(attrs().type("button").onclick("handleClick()"), "Click Me")
// Disabled button
button(attrs().disabled(), "Cannot Click")
// Button with icon
button(attrs().class_("btn-icon"),
svg(...), // Icon SVG
span("Download")
)Form Validation Attributes
HTML5 validation with type-safe attributes.
input(attrs()
.type("text")
.name("username")
.required() // Must be filled
.minlength(3) // Minimum 3 characters
.maxlength(20) // Maximum 20 characters
.pattern("[a-zA-Z0-9]+") // Regex pattern
.autocomplete("username") // Browser autocomplete hint
)
input(attrs()
.type("email")
.name("email")
.required()
.placeholder("you@example.com")
)
input(attrs()
.type("number")
.name("age")
.min(18) // Minimum value
.max(120) // Maximum value
.step(1) // Step increment
)Complete Form Example
A complete registration form with validation.
form(attrs().action("/register").method("POST").class_("form"),
// Username field
div(class_("field"),
label(attrs().for_("username"), "Username"),
input(attrs()
.type("text")
.id("username")
.name("username")
.required()
.minlength(3)
.maxlength(20)
.autocomplete("username")
)
),
// Email field
div(class_("field"),
label(attrs().for_("email"), "Email"),
input(attrs()
.type("email")
.id("email")
.name("email")
.required()
.autocomplete("email")
)
),
// Password field
div(class_("field"),
label(attrs().for_("password"), "Password"),
input(attrs()
.type("password")
.id("password")
.name("password")
.required()
.minlength(8)
.autocomplete("new-password")
)
),
// Terms checkbox
div(class_("field-checkbox"),
input(attrs()
.type("checkbox")
.id("terms")
.name("terms")
.required()
),
label(attrs().for_("terms"), "I agree to the terms")
),
// Submit
button(attrs().type("submit").class_("btn-primary"), "Register")
)Modern HTML5 Elements
Modern interactive elements with built-in browser functionality.
Dialog (Modal)
Native modal dialogs with backdrop and close behavior.
import static com.osmig.Jweb.framework.elements.DialogHelper.*;
// Define the dialog
dialog(attrs().id("confirm-dialog"),
h2("Confirm Action"),
p("Are you sure you want to proceed?"),
div(class_("dialog-actions"),
button(attrs().onclick(close("confirm-dialog")), "Cancel"),
button(attrs().onclick(close("confirm-dialog", "confirmed")), "Confirm")
)
)
// Open as modal (with backdrop)
button(attrs().onclick(showModal("confirm-dialog")), "Open Modal")
// Open as non-modal
button(attrs().onclick(show("confirm-dialog")), "Open Non-Modal")
// Toggle
button(attrs().onclick(toggle("confirm-dialog")), "Toggle")
// Close on backdrop click
dialog(attrs()
.id("my-dialog")
.onclick(closeOnBackdropClick("my-dialog")),
div(class_("dialog-content"),
h2("Click outside to close"),
p("Dialog content here")
)
)Details & Summary (Accordion)
Expandable content sections with native toggle behavior.
import static com.osmig.Jweb.framework.elements.DetailsHelper.*;
// Basic collapsible
details(
summary("Click to expand"),
p("Hidden content revealed when expanded")
)
// Open by default
details(attrs().open(),
summary("Already expanded"),
p("Visible content")
)
// FAQ Accordion (exclusive - using name attribute)
div(class_("faq"),
details(attrs().name("faq"),
summary("What is JWeb?"),
p("JWeb is a pure Java web framework...")
),
details(attrs().name("faq"),
summary("How do I get started?"),
p("Add the dependency and create a page...")
),
details(attrs().name("faq"),
summary("Is it production ready?"),
p("Yes, JWeb is production ready...")
)
)
// Control with JavaScript helpers
button(attrs().onclick(openAll("faq")), "Expand All")
button(attrs().onclick(closeAll("faq")), "Collapse All")
button(attrs().onclick(openExclusive("faq-1", "faq")), "Open First Only")Progress Bar
Display progress of a task.
// Determinate progress (known completion)
progress(attrs().value(70).set("max", "100"))
// Shorthand
progress(70, 100) // 70% complete
// Indeterminate (unknown completion)
progressIndeterminate() // Animated, no value
// Styled with label
div(class_("progress-container"),
label("Uploading..."),
progress(45, 100),
span("45%")
)Meter
Display a scalar measurement within a known range.
// Basic meter
meter(attrs()
.value(0.6)
.set("min", "0")
.set("max", "1")
)
// Shorthand
meter(0.6, 0, 1) // 60% of range
// With thresholds (browser applies colors)
meter(attrs()
.value(75)
.set("min", "0")
.set("max", "100")
.low(25) // Below this is "low"
.high(75) // Above this is "high"
.optimum(50) // Optimal value
)
// Examples
div(
label("Disk usage: "),
meter(0.8, 0, 1), // 80% - shown as warning (high)
span(" 80%")
)
div(
label("Battery: "),
meter(0.2, 0, 1), // 20% - shown as danger (low)
span(" 20%")
)Template & Slot (Web Components)
Define reusable HTML templates and content slots.
// Template (not rendered, used by JavaScript)
template(attrs().id("card-template"),
div(class_("card"),
h3(class_("card-title")),
p(class_("card-content")),
slot("actions") // Named slot
)
)
// Slot for web components
slot() // Default slot
slot("header") // Named slot
// Output (for calculation results)
form(attrs().oninput("result.value = a.valueAsNumber + b.valueAsNumber"),
input(attrs().type("number").name("a").value("0")),
text(" + "),
input(attrs().type("number").name("b").value("0")),
text(" = "),
output(attrs().name("result").set("for", "a b"), "0")
)Time & Data
Semantic elements for machine-readable dates and values.
// Time with machine-readable datetime
timeWithDatetime("2026-01-21", "January 21, 2026")
// Renders: <time datetime="2026-01-21">January 21, 2026</time>
// Event time
p("The concert starts at ",
timeWithDatetime("20:00", "8 PM"),
".")
// Duration
p("Flight duration: ",
timeWithDatetime("PT2H30M", "2 hours 30 minutes")
)
// Data (machine-readable value)
data("12345", "Product Name")
// Renders: <data value="12345">Product Name</data>
// Useful for product IDs, prices, etc.
p("Price: ", data("99.99", "$99.99"))Text Direction
Control text direction for internationalization.
// BDI - Bi-Directional Isolation
// Isolates text that might have different direction
p("User ", bdi(username), " posted this comment")
// Prevents RTL usernames from affecting surrounding text
// BDO - Bi-Directional Override
// Force specific direction
bdo(attrs().dir("rtl"), "This text is right-to-left")
bdo(attrs().dir("ltr"), "This text is left-to-right")Ruby Annotation
For East Asian typography - pronunciation guides above characters.
// Ruby annotation for Japanese/Chinese
ruby(
text("漢"),
rp("("),
rt("かん"), // Reading/pronunciation
rp(")")
)
// Multiple characters
ruby(
text("東京"),
rp("("),
rt("とうきょう"),
rp(")")
)
// Renders with pronunciation above the charactersEnhanced HTML Elements
Additional semantic elements for popovers, responsive images, definitions, text annotations, and forms.
Popover API
Native popover elements with auto-dismiss and backdrop behavior.
import static com.osmig.Jweb.framework.elements.PopoverElements.*;
import static com.osmig.Jweb.framework.elements.El.*;
// Auto popover (dismisses on click outside or Escape)
popoverToggleButton("my-popup", "Toggle Menu"),
autoPopover("my-popup",
ul(
li("Option 1"),
li("Option 2"),
li("Option 3")
)
)
// Manual popover (only dismisses programmatically)
popoverShowButton("info-pop", "Show Info"),
popoverHideButton("info-pop", "Close"),
manualPopover("info-pop",
p("This stays open until explicitly closed.")
)
// Popover with attributes on custom elements
div(attrs().attr(popover()).id("custom-pop"),
p("Custom popover content")
)
button(attrs().attr(popoverTarget("custom-pop")),
"Toggle Custom"
)
// JavaScript control
// showPopover("my-popup")
// hidePopover("my-popup")
// togglePopover("my-popup")Responsive Images
Art direction and format selection for optimized image delivery.
import static com.osmig.Jweb.framework.elements.PictureElements.*;
import static com.osmig.Jweb.framework.elements.El.*;
// Art direction: different images per viewport
picture(
source(srcset("hero-wide.jpg"), media("(min-width: 1024px)")),
source(srcset("hero-medium.jpg"), media("(min-width: 640px)")),
img("hero-small.jpg", "Hero image")
)
// Format selection: modern formats with fallback
picture(
source(srcset("photo.avif"), type("image/avif")),
source(srcset("photo.webp"), type("image/webp")),
img("photo.jpg", "Photo")
)
// Responsive with density descriptors
responsiveImg("photo.jpg", "Photo", "photo-2x.jpg")
// Renders: <img src="photo.jpg" srcset="photo.jpg 1x,photo-2x.jpg 2x">
// Lazy-loaded image with CLS prevention
lazyImg("hero.jpg", "Hero", 800, 600)
// Renders: <img src="hero.jpg" loading="lazy" width="800" height="600">
// Loading attributes
lazyLoad() // loading="lazy"
eagerLoad() // loading="eager"
fetchPriority("high")
decoding("async")Figure and Caption
Semantic container for self-contained content with optional captions.
import static com.osmig.Jweb.framework.elements.El.*;
// Image with caption
figure(
img("chart.png", "Sales chart"),
figcaption("Figure 1: Quarterly sales data")
)
// Code listing with caption
figure(attrs().class_("code-example"),
pre(code("const greeting = 'Hello World';")),
figcaption("Example: Variable declaration")
)
// Blockquote with attribution
figure(
blockquote("To be or not to be, that is the question."),
figcaption("William Shakespeare")
)Definition Lists
Semantic element for term-definition pairs, glossaries, and key-value data.
import static com.osmig.Jweb.framework.elements.El.*;
// Glossary
dl(
dt("HTML"), dd("HyperText Markup Language"),
dt("CSS"), dd("Cascading Style Sheets"),
dt("JS"), dd("JavaScript")
)
// Metadata / key-value pairs
dl(attrs().class_("metadata"),
dt("Author"), dd("Jane Doe"),
dt("Published"), dd("2026-01-29"),
dt("Category"), dd("Technology")
)Interactive Text Elements
Semantic inline elements for abbreviations, citations, quotations, keyboard input, and text annotations.
import static com.osmig.Jweb.framework.elements.El.*;
// Abbreviation with tooltip expansion
p("The ", abbr("HTML", "HyperText Markup Language"), " spec")
// Hovering shows "HyperText Markup Language"
// Definition term
p("A ", dfn("closure"), " captures its lexical scope.")
// Citation (title of a work)
p("As described in ", cite("The Art of Programming"), "...")
// Inline quotation (auto-adds quotes)
p("She said, ", q("Hello World"), " and it ran.")
// Blockquote with source URL
blockquote("https://example.com",
p("Knowledge is power.")
)
// Keyboard input
p("Press ", kbd("Ctrl"), "+", kbd("C"), " to copy.")
// Sample output
p("The program outputs: ", samp("Hello World"))
// Variable
p("Let ", var_("x"), " = 5")
// Highlighted text
p("Search results for: ", mark("JWeb framework"))
// Subscript / Superscript
p("H", sub("2"), "O") // H₂O
p("E = mc", sup("2")) // E = mc²
// Inserted / Deleted text
p(del("Old price: $50"), " ", ins("New price: $30"))
// Strikethrough (no longer accurate)
p(s("Available in stores"), " — Now online only!")Form Enhancements
Modern form elements for autocomplete, grouping, and specialized inputs.
import static com.osmig.Jweb.framework.elements.FormEnhancements.*;
import static com.osmig.Jweb.framework.elements.El.*;
// Autocomplete with datalist
input(attrs().attr("list", "browsers").name("browser")),
datalist("browsers",
option("Chrome", "Chrome"),
option("Firefox", "Firefox"),
option("Safari", "Safari"),
option("Edge", "Edge")
)
// Grouped options in select
select(attrs().name("car"),
optgroup("Swedish Cars",
option("volvo", "Volvo"),
option("saab", "Saab")),
optgroup("German Cars",
option("bmw", "BMW"),
option("audi", "Audi"))
)
// Fieldset with legend (groups related controls)
fieldset(
legend("Personal Information"),
label("Name:"),
input(attrs().type("text").name("name")),
label("Email:"),
input(attrs().type("email").name("email"))
)
// Specialized input types
colorInput("theme-color", "#3b82f6") // Color picker
dateInput("birthday") // Date picker
dateInput("event", "2026-01-01", "2026-12-31") // With range
timeInput("meeting") // Time picker
datetimeInput("appointment") // Date + time
monthInput("start-month") // Month picker
weekInput("sprint-week") // Week picker
rangeInput("volume", 0, 100, 50) // Slider
rangeInput("opacity", 0, 100, 50, 5) // Slider with stepSVG Elements
Create scalable vector graphics inline with type-safe methods.
// Import SVG elements
import static com.osmig.Jweb.framework.elements.El.*;
import static com.osmig.Jweb.framework.elements.SVGElements.*;Basic SVG
Create an SVG container with viewBox.
// SVG container
svg(viewBox("0 0 24 24"), width(24), height(24),
path(d("M12 2L2 7l10 5 10-5-10-5z"), fill("currentColor"))
)
// Alternative viewBox syntax
svg(viewBox(0, 0, 100, 100),
circle(cx(50), cy(50), r(40), fill("blue"))
)Shape Elements
Basic shapes for building graphics.
// Circle
svg(viewBox("0 0 100 100"),
circle(cx(50), cy(50), r(40),
fill("blue"),
stroke("black"),
strokeWidth(2)
)
)
// Rectangle
svg(viewBox("0 0 200 100"),
rect(x(10), y(10), width(180), height(80),
fill("red"),
stroke("black"),
strokeWidth(1)
)
)
// Rounded rectangle
svg(viewBox("0 0 200 100"),
rect(x(10), y(10), width(180), height(80),
rx(10), ry(10), // Corner radius
fill("green")
)
)
// Line
svg(viewBox("0 0 100 100"),
line(x1(10), y1(10), x2(90), y2(90),
stroke("black"),
strokeWidth(2)
)
)
// Polyline (connected lines, open)
svg(viewBox("0 0 100 100"),
polyline(points("10,90 50,10 90,90"),
fill("none"),
stroke("purple"),
strokeWidth(2)
)
)
// Polygon (connected lines, closed)
svg(viewBox("0 0 100 100"),
polygon(points("50,10 90,90 10,90"),
fill("yellow"),
stroke("black")
)
)Path Element
Complex shapes using path commands.
// Path with commands
svg(viewBox("0 0 24 24"),
path(
d("M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"),
fill("none"),
stroke("currentColor"),
strokeWidth(2),
strokeLinecap("round"),
strokeLinejoin("round")
)
)
// Path commands:
// M = moveto
// L = lineto
// H = horizontal lineto
// V = vertical lineto
// C = curveto (cubic bezier)
// S = smooth curveto
// Q = quadratic bezier
// T = smooth quadratic bezier
// A = arc
// Z = closepath
// Icon example (checkmark)
svg(viewBox("0 0 24 24"), width(24), height(24),
path(
d("M5 13l4 4L19 7"),
fill("none"),
stroke("green"),
strokeWidth(2)
)
)Grouping & Transforms
Group elements and apply transformations.
// Group elements
svg(viewBox("0 0 200 200"),
g(transform(translate(100, 100)),
circle(cx(0), cy(0), r(50), fill("blue")),
circle(cx(30), cy(30), r(20), fill("red"))
)
)
// Multiple transforms
g(transform(translate(50, 50), rotate(45), scale(1.5)),
rect(x(-25), y(-25), width(50), height(50), fill("purple"))
)
// Transform functions
translate(100, 50) // Move position
rotate(45) // Rotate degrees
scale(1.5) // Uniform scale
scale(2, 0.5) // Non-uniform scale
skewX(10) // Skew horizontally
skewY(10) // Skew verticallyDefinitions & Reuse
Define reusable elements and gradients.
svg(viewBox("0 0 200 200"),
// Define reusable elements
defs(
// Gradient
linearGradient(attrs().id("gradient1"),
stop(attrs().offset("0%").stopColor("blue")),
stop(attrs().offset("100%").stopColor("purple"))
),
// Reusable symbol
symbol(attrs().id("star").viewBox("0 0 24 24"),
path(d("M12 2l3 7h7l-5.5 4.5L18 21l-6-4-6 4 1.5-7.5L2 9h7z"))
)
),
// Use gradient
rect(x(10), y(10), width(80), height(80),
fill("url(#gradient1)")
),
// Reuse symbol
use(href("#star"), x(100), y(100), width(50), height(50))
)Icon Example
Create a complete icon.
// Reusable icon method
public static Element icon(String name) {
return switch (name) {
case "check" -> svg(viewBox("0 0 24 24"), width(24), height(24),
path(d("M5 13l4 4L19 7"),
fill("none"), stroke("currentColor"), strokeWidth(2))
);
case "x" -> svg(viewBox("0 0 24 24"), width(24), height(24),
path(d("M6 6l12 12M6 18L18 6"),
fill("none"), stroke("currentColor"), strokeWidth(2))
);
case "menu" -> svg(viewBox("0 0 24 24"), width(24), height(24),
path(d("M3 12h18M3 6h18M3 18h18"),
fill("none"), stroke("currentColor"), strokeWidth(2))
);
default -> fragment();
};
}
// Usage
button(class_("icon-btn"),
icon("check"),
span("Confirm")
)Attributes API
The attrs() builder provides type-safe, fluent attribute building.
Basic Attributes
// Core attributes
div(attrs()
.id("main")
.class_("container")
.title("Tooltip text")
)
// Shorthand for common attributes
div(id("main")) // Just id
div(class_("container")) // Just class
a(href("/home"), "Home") // Just hrefMultiple Classes
Add classes individually, conditionally, or as a list.
// Add classes
attrs().class_("btn").addClass("primary").addClass("lg")
// Multiple classes at once
attrs().classes("btn", "primary", "lg")
// Conditional classes
attrs()
.class_("btn")
.classIf("active", isActive) // Add if true
.classIf("disabled", isDisabled)
// Toggle between classes
attrs().classToggle(isOpen, "open", "closed")
// Complex conditional
attrs()
.class_("card")
.classIf("featured", product.isFeatured())
.classIf("soldout", product.getStock() == 0)
.classToggle(expanded, "expanded", "collapsed")Inline Styles
Three ways to add inline styles.
// 1. Lambda syntax (recommended - no .done() needed)
div(attrs()
.class_("card")
.style(s -> s
.display(flex)
.padding(rem(1))
.backgroundColor(white)
.borderRadius(px(8))
),
content
)
// 2. Continuing attributes after style with .done()
a(attrs()
.style()
.color(blue)
.textDecoration(none)
.done()
.href("/home")
.class_("nav-link"),
"Home"
)
// 3. Pass a Style object
Style cardStyle = style()
.padding(rem(1.5))
.backgroundColor(white)
.borderRadius(px(8));
div(attrs().style(cardStyle), content)Layout Shortcuts
Quick flexbox and grid setup.
// Flexbox helpers
attrs().flexCenter() // Flex + center items
attrs().flexColumn("1rem") // Column with gap
attrs().flexRow("1rem") // Row with gap
attrs().flexBetween() // Space-between + center
// Grid helpers
attrs().gridCols(3, "1rem") // 3-column grid with gap
attrs().gridCols(4) // 4-column grid without gap
// Example
div(attrs().flexRow("1rem"),
card1,
card2,
card3
)
div(attrs().gridCols(3, "2rem"),
each(products, this::renderCard)
)Data & ARIA Attributes
Custom data attributes and accessibility.
// Data attributes
attrs()
.data("user-id", "123")
.data("role", "admin")
.data("active", "true")
// Renders: data-user-id="123" data-role="admin" data-active="true"
// ARIA attributes
attrs()
.aria("label", "Close dialog")
.aria("expanded", "true")
.aria("controls", "menu-panel")
// Renders: aria-label="Close dialog" aria-expanded="true" ...
// Role
attrs().role("button")
attrs().role("navigation")
attrs().role("dialog")
// Combining for accessible button
button(attrs()
.class_("icon-btn")
.aria("label", "Close")
.title("Close window"),
icon("x")
)Boolean Attributes
Attributes that don't need values.
// Form attributes
input(attrs()
.disabled() // Cannot interact
.readonly() // Can't edit, can submit
.required() // Must fill before submit
.checked() // For checkboxes/radios
.autofocus() // Focus on page load
.multiple() // Allow multiple selections
)
// Media attributes
video(attrs()
.controls() // Show playback controls
.autoplay() // Start playing
.loop() // Loop playback
.muted() // Start muted
)
// Other
details(attrs().open()) // Expanded by default
script(attrs().defer()) // Defer loading
script(attrs().async()) // Async loading
option(attrs().selected()) // Pre-selected optionLink & Navigation
Attributes for links and navigation.
// External link (secure)
a(attrs()
.href("https://external.com")
.targetBlank() // target="_blank" rel="noopener noreferrer"
)
// Download link
a(attrs()
.href("/file.pdf")
.download() // Download instead of navigate
)
// Download with filename
a(attrs()
.href("/report-2026.pdf")
.download("annual-report.pdf") // Custom filename
)
// Rel attributes
a(attrs()
.href("/next")
.rel("next") // Pagination hint
)
a(attrs()
.href("https://sponsor.com")
.rel("sponsored") // Paid link (SEO)
)Image Attributes
Comprehensive image handling.
// Basic with alt
img(attrs()
.src("/images/hero.jpg")
.alt("Hero image description")
)
// Lazy loading
img(attrs()
.src("/images/photo.jpg")
.alt("Photo")
.loading("lazy") // Lazy load when near viewport
)
// Dimensions (prevents layout shift)
img(attrs()
.src("/images/product.jpg")
.alt("Product")
.width("800")
.height("600")
)
// Responsive images
img(attrs()
.src("/images/photo.jpg")
.srcset("/images/photo-2x.jpg 2x, /images/photo-3x.jpg 3x")
.alt("Photo")
)
// Picture element for art direction
picture(
source(attrs().media("(min-width: 800px)").srcset("/large.jpg")),
source(attrs().media("(min-width: 400px)").srcset("/medium.jpg")),
img(attrs().src("/small.jpg").alt("Responsive image"))
)Event Handlers
Attach JavaScript event handlers.
// Click handlers
button(attrs().onclick("handleClick()"), "Click")
button(attrs().onclick("deleteItem(" + id + ")"), "Delete")
// Form events
form(attrs().onsubmit("return handleSubmit(event)"))
input(attrs().onchange("updateValue(this.value)"))
input(attrs().oninput("search(this.value)"))
input(attrs().onfocus("showHint()"))
input(attrs().onblur("hideHint()"))
// Mouse events
div(attrs()
.onmouseenter("showTooltip()")
.onmouseleave("hideTooltip()")
)
// Keyboard events
input(attrs().onkeydown("handleKey(event)"))
input(attrs().onkeyup("handleKeyUp(event)"))
// Using JS DSL actions
import static com.osmig.Jweb.framework.js.Actions.*;
button(attrs().onclick(show("panel")), "Show")
button(attrs().onclick(hide("modal")), "Close")
button(attrs().onclick(toggle("dropdown")), "Toggle")Custom Attributes
Set any attribute not in the API.
// Generic set method
attrs().set("custom-attr", "value")
// HTMX attributes
attrs()
.set("hx-get", "/api/data")
.set("hx-target", "#result")
.set("hx-swap", "innerHTML")
// Alpine.js
attrs()
.set("x-data", "{ open: false }")
.set("x-show", "open")
.set("@click", "open = !open")
// Any attribute
attrs()
.set("itemscope", "")
.set("itemtype", "https://schema.org/Product")Conditional Rendering
Show or hide content based on conditions.
when() - Simple Conditional
Render content only when a condition is true.
// Lazy evaluation (recommended for expensive operations)
when(isLoggedIn, () -> span("Welcome back!"))
// Eager evaluation (for simple values)
when(isLoggedIn, span("Welcome, " + username))
// Examples
div(
h1("Dashboard"),
when(isAdmin, () -> adminPanel()),
when(hasNotifications, () -> notificationBadge(count))
)when().then().otherwise() - If/Else
Render different content for true and false conditions.
// If/else pattern
when(isLoggedIn)
.then(userMenu())
.otherwise(loginButton())
// In context
header(
h1("My App"),
nav(
a("/", "Home"),
when(isLoggedIn)
.then(a("/dashboard", "Dashboard"))
.otherwise(a("/login", "Sign In"))
)
)when().then().elif() - Multiple Conditions
Chain multiple conditions like if/else-if/else.
// Multiple conditions
when(isAdmin)
.then(adminPanel())
.elif(isModerator, modPanel())
.elif(isUser, userPanel())
.otherwise(guestPanel())
// Status display
when(status.equals("success"))
.then(successMessage())
.elif(status.equals("warning"), warningMessage())
.elif(status.equals("error"), errorMessage())
.otherwise(infoMessage())
// Without fallback (renders nothing if all false)
when(showPromo)
.then(promoBanner())
.end()match() - Pattern Matching
Match against multiple conditions in a clean syntax.
// Match pattern
match(
cond(isAdmin, adminPanel()),
cond(isModerator, modPanel()),
cond(isUser, userPanel()),
otherwise(loginPrompt())
)
// User type display
div(
match(
cond(user.isPremium(), premiumBadge()),
cond(user.isVerified(), verifiedBadge()),
otherwise(standardBadge())
)
)ternary() - Inline Conditional
For simple inline conditions.
// Ternary pattern
span(ternary(isActive, "Active", "Inactive"))
// With elements
div(ternary(hasError,
span(class_("error"), errorMessage),
span(class_("success"), "All good!")
))
// Styling
div(attrs().class_(ternary(isDark, "dark-theme", "light-theme")))each() - Iteration
Render a list of items.
// Basic iteration
List<String> items = List.of("Apple", "Banana", "Cherry");
ul(
each(items, item -> li(item))
)
// With complex rendering
List<User> users = getUsers();
div(class_("user-list"),
each(users, user ->
div(class_("user-card"),
img(user.getAvatar(), user.getName()),
h3(user.getName()),
p(user.getEmail()),
when(user.isVerified(), () -> verifiedBadge())
)
)
)
// With index (if needed)
ul(
each(items, (item, index) ->
li(attrs().data("index", String.valueOf(index)),
strong((index + 1) + ". "),
text(item)
)
)
)Combining Conditionals
Use conditionals together for complex UIs.
// Complex dashboard
div(class_("dashboard"),
// Header with conditional user display
header(
h1("Dashboard"),
when(currentUser != null)
.then(userDropdown(currentUser))
.otherwise(a("/login", "Sign In"))
),
// Main content with role-based rendering
main(
match(
cond(isAdmin, adminContent()),
cond(isEditor, editorContent()),
otherwise(viewerContent())
)
),
// Conditional sidebar
when(showSidebar, () ->
aside(
h3("Quick Links"),
each(quickLinks, link ->
a(link.getUrl(), link.getTitle())
)
)
),
// Footer with conditional elements
footer(
p("© 2026"),
when(showDebugInfo, () -> debugPanel())
)
)Null Safety
Handle potentially null values safely.
// Using when() for null checks
when(user != null, () ->
div(
h2("Welcome, " + user.getName()),
p(user.getBio())
)
)
// With Optional
Optional<User> maybeUser = findUser(id);
maybeUser.map(user ->
div(h2(user.getName()))
).orElse(
div(p("User not found"))
)
// Null-safe chaining in rendering
div(
h2(user != null ? user.getName() : "Guest"),
when(user != null && user.getAddress() != null, () ->
p(user.getAddress().getCity())
)
)Fragments
Group elements without adding a wrapper element to the DOM.
// fragment() groups elements without a wrapper
fragment(
h1("Title"),
p("First paragraph"),
p("Second paragraph")
)
// Useful in conditionals
when(showExtra, () -> fragment(
p("Extra info 1"),
p("Extra info 2"),
p("Extra info 3")
))
// Return multiple elements from a method
public Element renderUserInfo(User user) {
return fragment(
span(attrs().class_("name"), text(user.getName())),
span(attrs().class_("email"), text(user.getEmail()))
);
}Raw HTML
Insert pre-rendered HTML when needed (use carefully).
// raw() inserts HTML directly (be careful with user input!)
raw("<svg>...</svg>")
// Safe usage: only with trusted/sanitized content
String sanitizedMarkdown = markdownToHtml(trustedContent);
div(attrs().class_("markdown-body"), raw(sanitizedMarkdown))