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")

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 JS

Container 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")
)

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")
)
Use the for_ attribute on labels to associate them with inputs by id for accessibility.

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 characters
Modern HTML5 elements work in all modern browsers. Dialog and details have native keyboard support and accessibility built-in.

Enhanced 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 step
Core elements (figure, dl, blockquote, fieldset, etc.) are available via El.* static import. Specialized helpers (popoverToggleButton, responsiveImg, colorInput, rangeInput, etc.) require importing from their specific module: PopoverElements, PictureElements, or FormEnhancements.

SVG 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 vertically

Definitions & 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")
)
SVG elements inherit color from CSS using currentColor. Set the parent's color property to style icons.

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 href

Multiple 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 option

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")
Use class_() instead of class() because 'class' is a reserved word in Java.

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())
    )
)
Use lazy evaluation () -> element() for expensive operations to avoid unnecessary computation when the condition is false.

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))
Never use raw() with user-provided content. It can lead to XSS vulnerabilities.