Skip to main content

Core Concepts: Dynamic Components

While Static Components are great for simple layouts, modern web development often requires more power. When your component needs data processing, dynamic attribute binding, custom slot processing, or client-side interactivity, you must create a Dynamic Component.

Dynamic components are powered by Coralite's core built-in plugin: defineComponent.

Upgrading a Component #

To turn a static component into a dynamic one, you simply append a <script type="module"> tag below your <template> and export defineComponent.

HTML
Code copied!
<template id="my-dynamic-component">
  <div>...</div>
</template>



<script type="module">  
  import { defineComponent } from 'coralite'
  
  export default defineComponent({
    // Configuration goes here
  })
</script>

Tokens: Build-time Evaluation #

In a static component, {{ data }} simply maps to an HTML attribute. In a dynamic component, you can define Tokens. Tokens evaluate data at build time (during server-side rendering).

Tokens can be static strings or computed functions. Computed functions receive a values object, which contains all the attributes passed to the component, plus page metadata.

HTML
Code copied!
<template id="greeting-card">
  <div class="card">
    <h2>{{ formattedGreeting }}</h2>
    <p>Status: {{ status }}</p>
  </div>
</template>

<script type="module">  
  import { defineComponent } from 'coralite'
  
  export default defineComponent({
    tokens: {
      // Static string token
      status: 'Active',
  
      // Computed function token receiving 'name' attribute
      formattedGreeting: ({ name }) => {
        const cleanName = name ? name.trim().toUpperCase() : 'GUEST'
        return `Welcome, ${cleanName}!`
      }
    }
  })
</script>

When used as <greeting-card name=" alice "></greeting-card>, the output will be "Welcome, ALICE!".

Slots: Server-Side Processing #

While static components use the standard <slot> tag blindly, defineComponent allows you to intercept and process slot content on the server before it's rendered.

Slot functions receive an array of parsed HTML nodes (slotNodes) and the current values. You can mutate tags, replace them, or map over them conditionally.

HTML
Code copied!
<template id="smart-list">
  <ul class="list">
          {{ items }}
        </ul>
</template>

<script type="module">  
  import { defineComponent } from 'coralite'
  
  export default defineComponent({
    slots: {
      // The slot name matches the token {{ items }} in the template
      items: (slotNodes, values) => {
        // Transform the content passed into the slot
        return slotNodes.map(node => {
          // If the user passed <li> elements, automatically add a class
          if (node.type === 'tag' && node.name === 'li') {
            node.attributes.class = 'list-item-styled'
          }
          return node
        })
      }
    }
  })
</script>

Server-Side Setup #

If your component needs to fetch data from an API or read files before rendering, use the client.setup function. It effectively replaces Top-Level Await (TLA) in component modules.

The object returned by setup is seamlessly merged into the component's values object, making that data available to tokens and client scripts.

HTML
Code copied!
<script type="module">  
  import { defineComponent } from 'coralite'
  
  export default defineComponent({
    client: {
      // This runs ON THE SERVER during the build process
      setup: async (values) => {
        const response = await fetch('https://api.example.com/data')
        const data = await response.json()
  
        // Merge this data into the component's values
        return {
          fetchedData: data.message
        }
      }
    },
    tokens: {
      // Now we can use the fetched data in our template
      apiMessage: ({ fetchedData }) => fetchedData
    }
  })
</script>

Client-Side Interactivity (The Browser Script) #

The client.script function is what makes Coralite components interactive. This function is serialized and bundled to run in the user's browser after the page loads.

The script receives a context object (CoraliteScriptContent) containing things like id, values, and helpers.

HTML
Code copied!
<template id="toggle-box">
  <div ref="box" class="box closed">
          {{ content }}
        </div>
  <button type="button" ref="toggleBtn">Toggle</button>
</template>

<script type="module">  
  import { defineComponent } from 'coralite'
  
  export default defineComponent({
    client: {
      // This runs IN THE BROWSER
      script: (context) => {
        // Extract the refs helper
        const { helpers } = context
        const refs = helpers.refs
  
        // Get DOM elements safely
        const box = refs('box')
        const btn = refs('toggleBtn')
  
        btn.addEventListener('click', () => {
          box.classList.toggle('closed')
          box.classList.toggle('open')
        })
      }
    }
  })
</script>

Notice the use of ref="box" and helpers.refs('box')? This is Coralite's safe way to query the DOM. Learn more in the Managing the DOM (Refs) guide.


For strict API type definitions, arguments, and return types, see the defineComponent API Reference.

Start Building with Coralite!

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