Build for the Web, With the Web
Coralite is a static site generator built around HTML modules and the Native Web.
How does it work?
Components
Discover how to register and use Coralite components, including state management and imperative APIs. Components Guide
src
pages
components
import { defineConfig } from 'coralite';
/**
* Project configuration.
* Defines where your pages and components are located,
* and where the final build should be placed.
*/
export default defineConfig({
output: 'dist',
components: 'src/components',
pages: 'src/pages'
});
<html>
<head>
<title>Coralite App</title>
</head>
<body>
<!-- Main entry point for the application -->
<h1>My Store Checkout</h1>
<!-- Using our custom smart checkout component -->
<checkout-card product-id="123" theme="dark"></checkout-card>
</body>
</html> <template id="checkout-card">
<div class="checkout-card" data-theme="{{ theme }}">
<header>
<h2>Product: {{ productId }}</h2>
<span hidden="{{ hideValidationMessage }}">Validating promo...</span>
</header>
<div class="price-breakdown">
<p>Base Price: {{ basePrice }}</p>
<p class="discount" hidden="{{ hideDiscountRow }}">Discount: -{{ discountAmount }}</p>
<hr />
<h3>Total: {{ finalPrice }}</h3>
</div>
<div class="controls">
<input ref="promo-input" type="text" placeholder="Enter promo code" />
<button ref="apply-btn" disabled="{{ isApplyingDiscount }}">Apply</button>
</div>
<button ref="checkout-btn" class="pay-btn">Pay Now</button>
</div>
</template>
<script type="module">
import { defineComponent } from 'coralite';
export default defineComponent({
// ATTRIBUTES: Public API
attributes: {
productId: {
type: String,
default: ''
},
theme: {
type: String,
default: 'light'
}
},
// SERVER: Initial State (SSR Ready)
server: () => {
return {
basePrice: 99.00,
promoCode: '',
discountAmount: 0,
isApplyingDiscount: false
};
},
// GETTERS: Pure Derived UI Data (Handling ALL logic and negation)
getters: {
// Math logic
finalPrice: (state) => {
return Math.max(0, state.basePrice - state.discountAmount).toFixed(2);
},
// Negation logic for the native 'hidden' attribute
hideValidationMessage: (state) => {
return state.isApplyingDiscount === false;
},
// Negation logic for the native 'hidden' attribute
hideDiscountRow: (state) => {
return state.promoCode === '';
}
},
// CLIENT: Browser Interactivity
client: ({ state, refs, observe }) => {
observe('productId', async (newId) => {
if (!newId) {
return;
}
const data = await fetch(`/api/products/${newId}/price`).then(r => r.json());
state.basePrice = data.price;
state.promoCode = '';
state.discountAmount = 0;
});
refs('apply-btn').addEventListener('click', async () => {
const code = refs('promo-input').value.trim();
if (!code) {
return;
}
state.isApplyingDiscount = true;
try {
const data = await fetch(`/api/promo/validate?code=${code}`).then(r => r.json());
state.promoCode = code;
state.discountAmount = data.discount;
} catch (err) {
alert('Invalid promo code');
} finally {
state.isApplyingDiscount = false;
}
});
refs('checkout-btn').addEventListener('click', () => {
alert(`Charging $${state.finalPrice} to your card!`);
});
}
});
</script>
<style>
.checkout-card { padding: 20px; border: 1px solid #ccc; border-radius: 8px; }
/* Relying on native data-attributes instead of dynamic classes */
.checkout-card[data-theme="dark"] { background: #1a1a1a; color: white; }
.discount { color: green; }
.pay-btn { width: 100%; margin-top: 20px; padding: 12px; background: blue; color: white; }
</style> Server-Side Rendering
Perform fast server-side rendering and data fetching easily. Server Rendering
src
pages
components
import { defineConfig } from 'coralite';
/**
* SSR is deeply integrated into Coralite.
* No special flags are needed to enable it.
*/
export default defineConfig({
output: 'dist',
components: 'src/components',
pages: 'src/pages'
}); <main>
<!-- Page content rendered on the server -->
<h1>Server-Side Rendering</h1>
<!-- Components can fetch data during server rendering -->
<user-profile user-id="42"></user-profile>
<hr>
<!-- External data fetching component -->
<data-fetcher></data-fetcher>
</main> <template id="user-profile">
<article>
<h2>{{ username }}</h2>
<p>{{ bio }}</p>
</article>
</template>
<script type="module">
import { defineComponent } from 'coralite';
/**
* The 'server' block runs during the build or request.
* It's perfect for fetching data from a database or local file.
* The returned object is merged into the component state.
*/
export default defineComponent({
attributes: {
userId: { type: Number }
},
server: async ({ state }) => {
// In a real app, fetch from a DB using the userId
const data = { name: 'Jane Doe', bio: 'Software Engineer' };
return {
username: data.name,
bio: data.bio
};
}
});
</script> <template id="data-fetcher">
<section>
<h3>Remote Content</h3>
<p>{{ message }}</p>
<small>Fetched at: {{ timestamp }}</small>
</section>
</template>
<script type="module">
import { defineComponent } from 'coralite';
// Top-level imports in the script block are server-side ONLY.
// They are automatically stripped from the client bundle.
import { readFileSync } from 'node:fs';
export default defineComponent({
server: async () => {
// Example of server-only capability: reading from filesystem
// or performing heavy computations away from the client.
return {
message: 'This data was loaded from the server environment.',
timestamp: new Date().toLocaleTimeString()
};
}
});
</script> <main>
<h1>Dashboard</h1>
<p>Welcome to the server-rendered dashboard.</p>
<section>
<!-- Page-level SSR can orchestrate multiple components -->
<user-profile user-id="1"></user-profile>
<data-fetcher></data-fetcher>
</section>
</main> Metadata & Page Variables
Learn how to use built-in page variables and metadata extraction. Page Variables Guide
src
pages
components
import { defineConfig } from 'coralite';
/**
* Metadata and Page Variables are handled by built-in plugins.
*/
export default defineConfig({
output: 'dist',
components: 'src/components',
pages: 'src/pages'
});
<html lang="en">
<head>
<!-- These standard tags are extracted by the metadata plugin -->
<title>About Coralite</title>
<meta name="description" content="A lightweight component framework.">
<meta name="author" content="Coralite Team">
</head>
<body>
<!-- Components can access page-level metadata -->
<meta-info></meta-info>
</body>
</html> <template id="meta-info">
<div>
<h1>{{ title }}</h1>
<p>Author: {{ author }}</p>
<p>Page URL: {{ path }}</p>
</div>
</template>
<script type="module">
import { defineComponent } from 'coralite';
/**
* The 'data' function provides access to the 'page' object,
* which contains metadata, URL information, and more.
*/
export default defineComponent({
data: ({ page }) => {
return {
title: page.meta.title,
author: page.meta.author,
path: page.url.pathname
};
}
});
</script> Plugins
Extend capabilities across your app by authoring and consuming plugins. Plugins Guide
src
plugins
pages
components
import { defineConfig } from 'coralite';
import store from './src/plugins/store.js';
/**
* Register plugins in the config to share logic or state
* across all components in your application.
*/
export default defineConfig({
output: 'dist',
components: 'src/components',
pages: 'src/pages',
plugins: [store]
}); import { definePlugin } from 'coralite';
/**
* Plugins follow a two-phase initialization.
* They can provide a 'context' that is injected into
* both 'server' and 'client' blocks of components.
*/
export default definePlugin({
name: 'store',
client: {
context: () => {
let cart = [];
// Phase 2 factory returns the actual API
return () => ({
getCart: () => cart,
addToCart: (item) => {
cart.push(item);
// Notify other components about the change
window.dispatchEvent(new CustomEvent('cart-updated'));
}
});
}
}
}); <main>
<h1>Coral Shop</h1>
<!-- Shared state consumer -->
<cart-total></cart-total>
<section>
<div class="product">
<h3>Blue Coral</h3>
<!-- Shared state producer -->
<cart-button item-name="Blue Coral"></cart-button>
</div>
<div class="product">
<h3>Red Coral</h3>
<cart-button item-name="Red Coral"></cart-button>
</div>
</section>
</main> <template id="cart-button">
<button ref="btn">Buy {{ itemName }}</button>
</template>
<script type="module">
import { defineComponent } from 'coralite';
/**
* This component interacts with the 'store' plugin.
* The plugin's API is available on the context object.
*/
export default defineComponent({
attributes: {
itemName: { type: String }
},
client: ({ store, state, refs }) => {
refs('btn').addEventListener('click', () => {
// Call the plugin's method
store.addToCart(state.itemName);
});
}
});
</script> <template id="cart-total">
<div class="cart-total">
Items in cart: {{ count }}
</div>
</template>
<script type="module">
import { defineComponent } from 'coralite';
/**
* Listens for changes in the shared store.
*/
export default defineComponent({
client: ({ store, state, signal }) => {
const update = () => {
state.count = store.getCart().length;
};
update();
// Standard browser events can be used for communication
window.addEventListener('cart-updated', update, { signal });
}
});
</script> Features
Build with HTML Modules
Progressive Enhancement
Accessibility and Security First
Powerful Plugin System
Free & Open Source
Coralite is MPL-2.0 Licensed and will always be free and open source.
Brought to you by Thomas David
Start Building with Coralite!
Use the scaffolding script to get jump started into your next project with Coralite
Copied commandline!