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>
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} />
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:
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>
Building a Dynamic Component
Here's a practical example of a Badge component with multiple style variants:
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:
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:
Passing Complex Data to Alpine.js
When you need to pass complex data from the server to Alpine.js, you can use JSON encoding:
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)
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>