Components
Components are the building blocks of LUAT applications. They encapsulate reusable UI elements and can accept props and render children.
Basic Components
Creating a Component
A component is simply a .luat
file that exports a render
function:
<!-- Button.luat -->
<script>
local variant = props.variant or "primary"
local size = props.size or "medium"
local classes = "btn btn-" .. variant .. " btn-" .. size
</script>
<button class={classes} type={props.type or "button"}>
{@render props.children?.()}
</button>
Using Components
Import components in the script block and use them like HTML elements:
<!-- Page.luat -->
<script>
local Button = require("components/Button")
</script>
<div class="page">
<Button variant="primary" size="large">
Click me!
</Button>
<Button variant="secondary">
Cancel
</Button>
</div>
Props
Passing Props
Props are passed as attributes on the component tag:
<script>
local Card = require("components/Card")
</script>
<Card
title="User Profile"
subtitle="Manage your account"
image="/avatar.jpg"
isActive={true}
/>
Spreading Props
You can use the spread operator (...
) to pass all properties from a table to a component:
<script>
local Card = require("components/Card")
local cardProps = {
title = "User Profile",
subtitle = "Manage your account",
image = "/avatar.jpg",
isActive = true
}
</script>
<Card {...cardProps} />
This is particularly useful when:
- Forwarding props from a parent component
- Applying common properties to multiple components
- Building reusable component configurations
You can combine multiple spreads and override specific properties:
<script>
local baseProps = { size = "medium", variant = "default" }
local themeProps = { variant = "primary", outlined = true }
</script>
<!-- Later spreads override earlier ones, and direct props override all spreads -->
<Button {...baseProps} {...themeProps} size="large" />
See Template Syntax: Props Spread Operator for more details.
Accessing Props
Inside the component, access props via the global props
table:
<!-- Card.luat -->
<script>
local title = props.title
local subtitle = props.subtitle or ""
local image = props.image
local isActive = props.isActive or false
</script>
<div class="card {isActive and 'active' or ''}">
{#if image}
<img src={image} alt={title} class="card-image">
{/if}
<div class="card-content">
<h3 class="card-title">{title}</h3>
{#if subtitle}
<p class="card-subtitle">{subtitle}</p>
{/if}
</div>
</div>
Default Props
Handle missing props with default values:
<script>
local variant = props.variant or "default"
local size = props.size or "medium"
local disabled = props.disabled or false
local children = props.children
</script>
Children
Rendering Children
Use {@render props.children?.()}
to render content passed between component tags:
<!-- Modal.luat -->
<script>
local title = props.title
local isOpen = props.isOpen or false
</script>
{#if isOpen}
<div class="modal-overlay">
<div class="modal">
<div class="modal-header">
<h2>{title}</h2>
<button class="modal-close">×</button>
</div>
<div class="modal-body">
{@render props.children?.()}
</div>
</div>
</div>
{/if}
Using Components with Children
Pass content between the opening and closing tags:
<script>
local Modal = require("components/Modal")
</script>
<Modal title="Confirm Action" isOpen={showModal}>
<p>Are you sure you want to delete this item?</p>
<div class="modal-actions">
<button>Cancel</button>
<button class="danger">Delete</button>
</div>
</Modal>
Component Composition
Nested Components
Components can use other components:
<!-- UserCard.luat -->
<script>
local Card = require("components/Card")
local Button = require("components/Button")
local Avatar = require("components/Avatar")
</script>
<Card title={props.user.name} subtitle={props.user.role}>
<div class="user-card-content">
<Avatar src={props.user.avatar} size="large" />
<div class="user-info">
<p>Email: {props.user.email}</p>
<p>Joined: {props.user.createdAt}</p>
</div>
<div class="user-actions">
<Button variant="primary">Edit</Button>
<Button variant="secondary">View Profile</Button>
</div>
</div>
</Card>
Layout Components
Create reusable layout components:
<!-- Page.luat -->
<script>
local AppBar = require("components/AppBar")
local Footer = require("components/Footer")
local currentNode = getContext("currentNode")
local pageContext = getContext("pageContext")
</script>
<html>
<head>
<title>{currentNode.properties.title}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div class="app">
<AppBar />
<main class="main-content">
{@render props.children?.()}
</main>
<Footer />
</div>
</body>
</html>
Component Context
Getting Context
Access shared context data using getContext()
:
<script>
local pageContext = getContext("pageContext")
local currentNode = getContext("currentNode")
local currentTheme = pageContext.currentTheme
</script>
<div class="component" data-theme={currentTheme}>
<h1>{currentNode.properties.title}</h1>
</div>
Available Context
Common context variables include:
pageContext
- Page-level data and configurationcurrentNode
- Current content node datacurrentTheme
- Active theme settings
Component Types
Module Exports
Components can export metadata using module scripts:
<!-- Hero.luat -->
<script module>
local type = "content:hero"
exports.type = type
</script>
<script>
local title = props.title
local subtitle = props.subtitle
local image = props.image
</script>
<div class="hero" style="background-image: url('{image}')">
<div class="hero-content">
<h1>{title}</h1>
<p>{subtitle}</p>
</div>
</div>
Advanced Patterns
Conditional Component Rendering
<script>
local Button = require("components/Button")
local Link = require("components/Link")
local ComponentToRender = props.href and Link or Button
</script>
<ComponentToRender href={props.href} onClick={props.onClick}>
{props.label}
</ComponentToRender>
Component Factories
<script>
local function createIcon(name)
return function(iconProps)
return string.format('<i class="icon icon-%s %s"></i>',
name, iconProps.class or "")
end
end
local HomeIcon = createIcon("home")
local UserIcon = createIcon("user")
</script>
<nav>
<a href="/home">{@html HomeIcon({class = "nav-icon"})}</a>
<a href="/profile">{@html UserIcon({class = "nav-icon"})}</a>
</nav>
Slots (Named Children)
Pass specific content to named slots:
<!-- Card.luat -->
<div class="card">
<div class="card-header">
{@render props.header?.()}
</div>
<div class="card-body">
{@render props.children?.()}
</div>
<div class="card-footer">
{@render props.footer?.()}
</div>
</div>
Usage:
<script>
local Card = require("components/Card")
local function renderHeader()
return "<h2>Card Title</h2>"
end
local function renderFooter()
return "<button>Action</button>"
end
</script>
<Card header={renderHeader} footer={renderFooter}>
<p>This is the main card content.</p>
</Card>
Best Practices
1. Keep components focused
Each component should have a single responsibility:
<!-- Good: Focused button component -->
<!-- Button.luat -->
<button class="btn btn-{props.variant}" disabled={props.disabled}>
{@render props.children?.()}
</button>
2. Use descriptive prop names
<!-- Good -->
<UserCard
user={userData}
showActions={true}
onEdit={handleEdit}
/>
<!-- Avoid -->
<UserCard
data={userData}
flag={true}
callback={handleEdit}
/>
3. Provide sensible defaults
<script>
local variant = props.variant or "primary"
local size = props.size or "medium"
local disabled = props.disabled or false
</script>
4. Document complex components
<!--
UserProfileCard.luat
Props:
- user (object): User data with name, email, avatar
- editable (boolean): Whether to show edit controls
- onSave (function): Callback when profile is saved
- onCancel (function): Callback when editing is cancelled
-->
<script>
local user = props.user
local editable = props.editable or false
</script>