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.

⚠️ Warning: AST Splicing (Host Tag Removal)

Just like static components, Coralite's server completely deletes the host tag of declarative dynamic components and replaces it with the template's inner HTML. If you try to style the host tag (e.g., <my-component class="foo">) in CSS or use it as a flex container, it will break because the tag simply won't exist in the final HTML.

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>

<!-- The addition of this script block makes it dynamic -->
<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!".

Best Practices for Data Binding #

Developers should not pass complex objects or arrays as stringified JSON attributes to components. Passing data='[{"id": 1}, {"id": 2}]' into an HTML attribute is a common anti-pattern in vanilla HTML/JS, but Coralite actively discourages it.

Instead, the framework expects developers to handle complex data structures in their server-side script logic (like mapping over an array) and pass only decomposed primitive values (strings, numbers, booleans) as individual attributes to their components.

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">
    <slot name="items"></slot>
  </ul>
</template>

<script type="module">  
  import { defineComponent } from 'coralite'
  
  export default defineComponent({
    slots: {
      // The slot name matches <slot name="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.

The object returned by setup is seamlessly merged into the component's values object, making that data available to tokens and client scripts. Keep in mind that anything returned by setup will be stringified using serialize-javascript before being sent to the client.

While setup is used for data fetching on the server, if you need true Top-Level Await (TLA) for client-side functionality (like awaiting a module initialization in the browser), you must use client.imports which is designed to handle TLA natively for the client.

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, root, and helpers. The root property has been removed. Plugins and scripts should query the document directly. Coralite strictly renders into the Light DOM, not the Shadow DOM, so elements inherit global CSS.

Reactivity & Imperative Web Components #

Coralite embraces a strictly SSR first approach. Statically placed HTML tags in your templates are not reactive. To use a true Web Component with a client-side lifecycle that reacts to attribute changes, you must utilize the Imperative Requirement.

Here is the standard pattern for declarative Reactivity:

HTML
Code copied!
<!-- 1. The Child Component (child-element.html) -->
<template id="child-element">
  <h3 ref="titleDisplay">{{ title }}</h3>
  <p>Status: {{ computedStatus }}</p>
</template>

<script type="module">  
  import { defineComponent } from 'coralite'
  
  export default defineComponent({
    tokens: {
      title: 'Default Title',
      computedStatus: (values) => (values.title === 'Active' ? 'Online' : 'Offline')
    },
    client: {
      // Setup runs computed tokens on the client
      setup: () => ({
        computedStatus: (values) => (values.title === 'Active' ? 'Online' : 'Offline')
      }),
      script: (context) => {
        console.log('Child mounted with title:', context.values.title)
      }
    }
  })
</script>
HTML
Code copied!
<!-- 2. The Parent Component (parent-element.html) -->
<template id="parent-element">
  <div ref="target"></div>
</template>

<script type="module">  
  import { defineComponent } from 'coralite'
  
  export default defineComponent({
    client: {
      // MUST declare the dynamically created component for the bundler
      components: ['child-element'],
      script: (context) => {
        const target = context.helpers.refs('target')
  
        // 1. Instantiate the component imperatively
        const child = document.createElement('child-element')
        child.setAttribute('title', 'Pending')
  
        // 2. Mount to DOM (Fires connectedCallback & renders DOM)
        target.replaceWith(child)
  
        // 3. Reactivity test: Updating attribute triggers MutationObserver & re-render
        setTimeout(() => {
          child.setAttribute('title', 'Active')
        }, 1000)
      }
    }
  })
</script>

Notice the use of ref="target" and context.helpers.refs('target')? This is Coralite's safe way to query the DOM. The refs plugin automatically queries the DOM using uniquely compiled IDs. 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

Copied commandline!