Coralite logo

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'
});
<!DOCTYPE html>
<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'
});
<!DOCTYPE html>
<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

Progressive enhancement

Build with HTML Modules

Build your entire website with modular components using only native web technologies. This provides a simple, stable, and maintainable foundation without third-party libraries.

Progressive Enhancement

Ensures that all basic content and functionality are accessible, even on browsers without JavaScript. JavaScript is treated as an optional enhancement, not a requirement.

Accessibility and Security First

By leveraging native HTML elements, sites inherit strong accessibility features out-of-the-box. A small footprint with no third-party library dependencies creates a more secure website.

Powerful Plugin System

Extend and customize the build process with a plugin API. Use hooks to manage content, create aggregations like blog posts with pagination, or transform final HTML and CSS output.

Free & Open Source

Coralite is MPL-2.0 Licensed and will always be free and open source.

Brought to you by Thomas David

Thomas David - Author of Coralite

Start Building with Coralite!

Use the scaffolding script to get jump started into your next project with Coralite

Copied commandline!