Components
Components are the building blocks of Coralite. They allow you to encapsulate HTML, CSS, and logic into reusable custom elements. Coralite supports two types of components: Static and Dynamic.
Static Components #
Static components are purely HTML-based elements without complex scripting logic. If a piece of UI doesn't need to fetch data, handle user events, or compute complex state, it should be a static component.
Component Definition #
Every component in Coralite is defined inside an HTML file using the standard <template> tag. The most important part of a component is its id attribute. This ID becomes the custom HTML tag name you will use to include the component in your pages. Note: All component IDs MUST contain a hyphen (e.g., my-header).
<!-- src/components/header.html -->
<template id="coralite-header">
<header class="site-header">
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
</header>
</template>
Using Components #
To use the component in your pages (or inside other components), the custom element tag name must exactly match the component's id.
Coralite components are server-side rendered (SSR). By default, Coralite preserves your custom host tag in the final HTML and injects the template content inside it. Learn more about how this works in the SSR Guide.
<!-- src/pages/index.html -->
<html lang="en">
<head>
<title>Home</title>
</head>
<body>
<!-- The tag name matches the template ID -->
<coralite-header></coralite-header>
<main>
<h1 class="display">Welcome to my site!</h1>
</main>
</body>
</html>
Basic Data Binding #
You can pass data into static components using standard HTML attributes. Coralite allows you to display these attribute values inside your component using double curly braces ({{ }}).
<!-- src/components/user-card.html -->
<template id="user-card">
<div class="card">
<!-- The 'name' attribute value will be injected here -->
<h2>{{ name }}</h2>
<!-- The 'role' attribute value will be injected here -->
<p class="role">{{ role }}</p>
</div>
</template>
Now, when using the component, simply pass the data as attributes:
<!-- src/pages/team.html -->
<body>
<h1 class="display">Our Team</h1>
<div class="team-grid">
<!-- Passing data via attributes -->
<user-card name="Alice Smith" role="Lead Developer"></user-card>
<user-card name="Bob Jones" role="Designer"></user-card>
</div>
</body>
Standard Slots #
Sometimes you need to pass entire blocks of HTML into a component, not just simple text attributes. Coralite supports standard HTML <slot> elements for this purpose.
<!-- src/components/alert-box.html -->
<template id="alert-box">
<div class="alert {{ type }}">
<div class="alert-content">
<!-- Content passed between tags goes here -->
<slot></slot>
</div>
</div>
</template>
Dynamic Components #
While static components are great for simple layouts, modern web development often requires more power. When your component needs data fetching, reactive state, or client-side interactivity, you must create a Dynamic Component.
Dynamic components are powered by the defineComponent plugin. They follow the Smart State, Dumb Template philosophy, where logic is strictly separated into five isolated blocks.
By default, Coralite preserves the host tag of dynamic components in the final HTML but applies display: contents via an internal stylesheet. This ensures the tag doesn't affect your layout while remaining available for hydration.
Note: If you use the no-hydration attribute, the host tag is completely removed (spliced) and replaced by its contents. Learn more in the SSR Guide.
Upgrading a Component #
To turn a static component into a dynamic one, append a <script type="module"> tag below your <template> and export defineComponent.
<template id="my-dynamic-component">
<div>...</div>
</template>
<!-- The addition of this script block makes it dynamic -->
<script type="module">
import { defineComponent } from 'coralite'
export default defineComponent({
attributes: {
// ...
},
async server () {
// ...
},
getters: {
//...
},
slots: {
//...
},
client: () => {
// ...
}
})
</script>
The Five Pillars of State #
Coralite unifies five different sources of data and logic into a single reactive state object available to your template and client blocks.
1. Attributes (Inputs) #
Attributes are the "props" of your component. They are coerced from HTML attributes into JS primitives and are automatically injected into your reactive state object.
Note: Attributes only support String, Number, and Boolean types. Object and Array types are blocked; use the server block for complex data.
attributes: {
theme: { type: String, default: 'light' },
count: { type: Number, default: 0 }
}
2. Server (Server-Side Logic) #
The server block runs strictly on the server during build-time. It's the place for database queries or API calls. See the SSR Guide for more details.
async server({ myPlugin }) {
const posts = await db.getPosts();
return { posts, pluginData: myPlugin.data };
}
3. Getters (Derived State) #
Getters calculate state based on other state. They are pure, synchronous, and reactive.
getters: {
postCount: (state) => state.posts.length
}
4. Slots (Light DOM Transformation) #
The slots block allows you to intercept and transform the Light DOM nodes passed to your component before they are rendered.
slots: {
default: (nodes, state) => {
// Wrap all nodes in a div if count is even
if (state.count % 2 === 0) {
return `<div class="wrapper">${nodes.map(n => n.outerHTML).join('')}</div>`;
}
return nodes;
}
}
Referencing Elements (Refs) #
Before writing client-side logic, you often need a way to target specific DOM elements inside your template without relying on brittle CSS classes or IDs. Coralite provides the ref attribute for this.
<template id="counter-btn">
<button ref="my-btn">Clicked <span>{{ count }}</span> times</button>
</template>
5. Client (Client Controller) #
The client block is where interactivity lives. It runs in the browser and has full read/write access to the state, as well as access to the element instance and unique IDs. You can easily select elements from your template using the refs function.
client: ({ state, signal, refs, root, instanceId }) => {
// 'my-btn' matches the ref="my-btn" in the template above
const btn = refs('my-btn');
btn.addEventListener('click', () => {
state.count++; // Mutating state triggers a re-render!
}, { signal });
}
The Flow of State #
It helps to visualize the Five Pillars as different input streams feeding into a single, unified State Proxy. Whether data comes from the server, an HTML attribute, or a client-side mutation, it all ends up in the same place.
- Initialization: When the page builds, Coralite gathers Attributes and executes the Server block.
- Serialization: This combined data is serialized into a JSON object and sent to the browser.
- Hydration: On the client, Coralite "wakes up" the component and merges the serialized data into a reactive Proxy.
- Mutation: When the Client block or a Getter mutates the state, the Proxy detects the change.
- Re-render: Coralite automatically updates the Template (and re-runs Slots if necessary) to reflect the new state.
Reactivity & Performance #
Coralite uses a Lazy Deep Proxy system. This means that even if you have massive datasets in your state, you only pay the performance cost for the parts of the state that you actually access or mutate. This ensures that Coralite stays fast even as your application grows.
Best Practices: Smart State, Dumb Template #
- Keep Templates Simple: Templates should only contain HTML and
{{ token }}placeholders. Avoid complex logic in HTML. - Prefer Primitives for Attributes: Only pass Strings, Numbers, and Booleans via attributes. Use
serverfor complex objects. - Clean Up with AbortSignal: Always pass the
signaltoaddEventListenerorfetchinside yourclientto prevent memory leaks.
Anti-Patterns: Respect the Component Boundary #
In Coralite, Pages are strictly consumers. To ensure your application remains maintainable and robust, you must respect the boundary between a page and its components.
- Avoid Page-Level Scripts for Component Logic: Do not use
<script>tags in standard.htmlpages to manipulate components. All logic related to a component's behavior should be encapsulated within the component itself. - Never Use querySelector on Component Internals: Attempting to use
document.querySelectorfrom a page to find an element inside a component is an anti-pattern. Because Coralite uses therefsystem to dynamically generate unique, namespaced selectors for every instance, relying on specific classes or IDs from a page is brittle and unreliable. - Do Not Manually Sync Attributes: Avoid using page-level JavaScript to manually update component attributes (e.g.,
setAttribute) to trigger changes. Instead, manage state within the component'sclientblock. - Avoid Logic Coordination in Pages: If multiple components need to coordinate (e.g., a search bar filtering a list), do not write this logic in the page. Instead, move both components into a parent component that handles the coordination logic.
For detailed API signatures, see the defineComponent API Reference.