octobercms/october · error · RuntimeException

Cannot end component: no active component.

Error message

Cannot end component: no active component.

What it means

ViewComponent::endComponent() (exposed as UiFactory::end()) pops the last component off the static stack, captures its final slot, and echoes the rendered result. When array_pop() returns null the stack was already empty, and it throws a RuntimeException: more end() calls than component starts in the current render. Typical markup imbalance — an extra @end / UiFactory::end(), or an end() outside a condition whose matching ->slot() open never ran.

Source

Thrown at modules/system/classes/ViewComponent.php:101

        // Close previous slot
        if ($instance->currentSlot !== null) {
            $instance->slots[$instance->currentSlot] = ob_get_clean();
        }

        // Start new slot
        ob_start();
        $instance->currentSlot = $name;
    }

    /**
     * endComponent captures the final slot and renders the component
     */
    public static function endComponent(): void
    {
        $instance = array_pop(static::$stack);

        if ($instance === null) {
            throw new \RuntimeException("Cannot end component: no active component.");
        }

        // Capture final slot
        if ($instance->currentSlot !== null) {
            $instance->slots[$instance->currentSlot] = ob_get_clean();
        }

        echo $instance->render();
    }
    /**
     * __call supports fluent API for setting props
     */
    public function __call(string $method, array $args): static
    {
        $this->props[$method] = $args[0] ?? true;
        return $this;
    }

View on GitHub (pinned to b608633a7e)

Solutions

  1. Count `->slot()` opens versus UiFactory::end() closes in the failing template and its partials — they must pair 1:1 on every code path
  2. Check conditionals/loops: each branch that renders an end() must also have rendered the open in the same branch
  3. Remove stray end() calls duplicated by includes/partials
  4. Prefer the inline `<?= UiFactory::button(...) ?>` form for simple components to avoid manual pairing entirely

Example fix

// before — extra end() with no matching open
<?php UiFactory::card(title: 'A')->slot() ?>
    <p>One</p>
<?php UiFactory::end() ?>
<?php UiFactory::end() ?>  <!-- RuntimeException: no active component -->
// after — one open, one close
<?php UiFactory::card(title: 'A')->slot() ?>
    <p>One</p>
<?php UiFactory::end() ?>
Defensive patterns

Strategy: type-guard

Type guard

function hasOpenViewComponent(): bool
{
    $prop = new ReflectionProperty(\System\Classes\ViewComponent::class, 'stack');
    return !empty($prop->getValue());
}

if (hasOpenViewComponent()) {
    \System\Classes\ViewComponent::endComponent();
} else {
    throw new LogicException('Unbalanced UiFactory template: extra end()');
}

Prevention

When it happens

Trigger: An extra UiFactory::end() in a template; an end() in an else-branch while the ->slot() open was in the if-branch; duplicated footer/partial that also contains an end(); calling endComponent() directly without ever calling ->slot().

Common situations: Copy-pasted partials that each carry their own end(); refactoring that leaves orphan close tags; conditionals that pair opens and closes asymmetrically.

Related errors


AI-assisted analysis of octobercms/october@b608633a7e (2026-08-21). Data as JSON: /api/errors/d1504e2b604ff3d1. Report an issue: GitHub.