Skip to main content

Dynamic Attributes & Classes

Luat provides a flexible and powerful system for manipulating HTML attributes and CSS classes dynamically. This allows you to build responsive, stateful components with ease, from simple dynamic properties to complex conditional styling.

Attribute Binding

You can bind attributes to dynamic values using the same curly brace syntax as text interpolation.

<script>
local imageUrl = "/images/profile.png"
local altText = "A portrait of the user"
local isDisabled = false
</script>

<img src={imageUrl} alt={altText} />
<button disabled={isDisabled}>Submit</button>
main.luat
Loading Luat...

Boolean Attributes

For boolean attributes like disabled, checked, or selected, the attribute is included when the value is true and omitted when false.

<input type="checkbox" checked={isSubscribed} />

<!-- Shorthand: equivalent to checked={checked} -->
<input type="checkbox" {checked} />
main.luat
Loading Luat...

Dynamic CSS Classes

Managing CSS classes is a common task, and Luat offers multiple approaches to make it declarative and intuitive.

String Concatenation

The simplest approach is concatenating class strings:

main.luat
Loading Luat...

Conditional Classes with Tables

The most powerful way to handle classes is by passing a Lua table to the class attribute. The keys are the class names, and the boolean values determine if they are included in the rendered HTML.

This makes it easy to mix static and dynamic classes, and to toggle classes based on component state.

<script>
local hasError = true
local isPrimary = false

local classes = {
["button"] = true, -- always include 'button'
["button-primary"] = isPrimary,
["button-error"] = hasError,
["font-bold text-lg"] = true -- multiple classes in one key
}
</script>

<div class={classes}>
Click Me
</div>

Result:

<div class="button button-error font-bold text-lg">
Click Me
</div>
main.luat
Loading Luat...

Building a Dynamic Component

Here's a practical example of a Badge component with multiple style variants:

Loading Luat...

Data Attributes

Dynamic data attributes are useful for storing custom data or integrating with JavaScript libraries. Click on the items below to see the data attributes in action:

main.luat
Loading Luat...

Integrating with Client-Side Libraries

Luat works seamlessly with client-side libraries like Alpine.js and htmx. The server renders the initial HTML with all the necessary attributes, and the client-side library takes over for interactivity.

Alpine.js Integration

Alpine.js is perfect for adding lightweight interactivity. Here's a live example of a counter and toggle:

main.luat
Loading Luat...

Passing Complex Data to Alpine.js

When you need to pass complex data from the server to Alpine.js, you can use JSON encoding:

main.luat
Loading Luat...

Form Handling with Alpine.js

This example demonstrates the power of server-side rendering combined with Alpine.js. Notice how:

  • All configuration lives in Lua - field definitions, labels, placeholders, validation messages, styling
  • Validation script is generated server-side - Luat compiles the JavaScript from field definitions
  • Form fields rendered via {#each} - Data-driven form generation
  • Alpine.js is minimal - only handles state (x-data, x-model, x-show)
main.luat
Loading Luat...

This technique gives you the full power of server-side rendering combined with rich client-side interactivity.

Best Practices

1. Extract complex class logic to variables

<script>
local isActive = props.isActive
local variant = props.variant

-- Keep template clean by computing classes in script
local buttonClasses = {
["btn"] = true,
["btn-active"] = isActive,
["btn-" .. variant] = variant ~= nil
}
</script>

<button class={buttonClasses}>Click me</button>

2. Use consistent naming conventions

<script>
-- Good: descriptive, consistent naming
local isDisabled = props.disabled or false
local hasError = props.error ~= nil
local isLoading = props.loading or false

-- Avoid: inconsistent or unclear naming
local disabled = props.d
local err = props.e
</script>

3. Provide fallback values

<script>
local variant = props.variant or "default"
local size = props.size or "medium"

local validVariants = { primary = true, secondary = true, default = true }
if not validVariants[variant] then
variant = "default"
end
</script>