Custom Components
Introduction
Components in ReeWeb are just .ree files in src/components/. There is no registration step, no lifecycle, and no framework to learn. This recipe walks through creating, using, and composing custom components.
The Simplest Component
Create src/components/greeting.ree:
<div class="greeting">
<h2>Hello, {= props.attributes.name }!</h2>
</div>
Use it in any template by writing a custom HTML element whose tag matches the file name:
<greeting name="World"></greeting>
<!-- Renders: <div class="greeting"><h2>Hello, World!</h2></div> -->
The element passes HTML attributes as props.attributes and slot content (the markup between the tags) as props.children. To pass a dynamic value rather than a literal string, interpolate the attribute: <greeting name="{= props.user.first_name }"></greeting>.
Component Props Pattern
A well-designed component destructures its expected props explicitly, then spreads the rest:
{{ const { children } = props;
const { class: _class, href, ...rest } = props.attributes || {}; }}
<a href="{= href }" class="btn {= _class || '' }" ...rest>
{~ children }
</a>
This pattern:
- Destructures known attributes (
class,href) for special handling - Spreads any additional attributes via
...rest(converts tokey_values()) - Renders slot content via
{~ children }(raw output - it's trusted template content)
Usage:
<link-button href="/about" class="large" data-track="nav-click"> About us </link-button>
Using Project Helpers in Components
Project helpers from src/lib/project_helpers.ts are available in components just like in templates:
{{ const { text, type } = props.attributes; }}
<div class="badge badge-{= type }">
{~ md(text) }
</div>
This renders inline markdown inside the component:
<badge type="info" text="**New** feature available!"></badge>
Composing Components
Components can include other components. A card component that uses the badge component:
{{ const { children } = props;
const { title, badge_text, badge_type, ...rest } = props.attributes || {}; }}
<div class="card" ...rest>
{#if title }
<h3>{= title }</h3>
{/if}
{#if badge_text }
<badge type="{= badge_type || 'info' }" text="{= badge_text }"></badge>
{/if}
<div class="card-body">
{~ children }
</div>
</div>
Usage:
<card-box title="Getting Started" badge_text="New" badge_type="green">
<p>Here's how to get started with ReeWeb.</p>
</card-box>
Accessing Translations in Components
Components have access to the full render context - including translations - through props:
<footer class="site-footer">
<p>{_ ui.site_name } - {_ nav.home }</p>
</footer>
There's no need to forward translation strings explicitly. The component inherits the parent's data automatically.
Testing Components Locally
To test a component during development:
- Create a test page in
src/public/test/that uses the component - Run
bun dev - Open
http://localhost:3000/test/ - Edit the component and see changes immediately (dev server reloads on file changes)
Once the component works as expected, use it from your real pages and remove the test page.