Templates Context API

Provide / Inject Context API

Inspired by Svelte's Context API and Vue's Provide/Inject mechanism, this feature allows parent components to share state down the entire component tree without explicitly passing it down as props through each intermediate child.

§ Core Concepts

When designing related component groups (like forms, select lists, accordion panels, or tab pages), child components often need access to values or state managed by the parent container.

Instead of forcing developers to pass the parent's data to each nested field explicitly, the parent component can provide a context mapping, which child elements can then inject on-demand.

§ Form Context Example

Let's build a form validation container that automatically shares its name and error state with all child input controls nested within it.

1. The Parent: Provide Context

Create a form field container component at resources/views/components/field/root.nimbus. It registers a context key using $context.provide():

<!-- views/components/field/root.nimbus -->
{{
  $context.provide('fieldContext', {
    name: name,
    id: id || name,
    hasError: session.HasError(name),
    errorMessage: session.Error(name)
  })
}}

<div class="form-group mb-4">
  {{{ .slots.main }}}
</div>

2. The Child: Inject Context

Create the input control at resources/views/components/input/control.nimbus. It accesses the provided properties using $context.inject():

<!-- views/components/input/control.nimbus -->
{{
  const field = $context.inject('fieldContext')
}}

<input 
  name="{{ field.name }}" 
  id="{{ field.id }}" 
  class="input {{ field.hasError ? 'border-red-500' : 'border-slate-300' }}"
  {{ $props.toAttrs() }}
/>

Similarly, write the label control at resources/views/components/field/label.nimbus to auto-associate the for="..." attribute:

<!-- views/components/field/label.nimbus -->
{{
  const field = $context.inject('fieldContext')
}}

<label for="{{ field.id }}" class="block font-medium text-slate-700 mb-1">
  {{{ .slots.main || text }}}
</label>

3. Consume the Components Together

Now, when building views, developers get automatic id-matching, name configuration, and error styling simply by nesting the tags:

@field.root({ name: 'email' })
  @!field.label({ text: 'Email Address' })
  @!input.control({ type: 'email', placeholder: 'you@example.com' })
@end

This compiles down into the following clean, error-aware markup without requiring the developer to pass name and id to both labels and inputs:

<div class="form-group mb-4">
  <label for="email" class="block font-medium text-slate-700 mb-1">Email Address</label>
  <input name="email" id="email" type="email" placeholder="you@example.com" class="input border-slate-300" />
</div>