HtmxRequest Showcase

See what htmx sends in request headers. Click buttons on the left to update the table on the right.

Trigger & Target

Different buttons set different trigger and target values

Prompt

Use hx-prompt to get user input - value appears in prompt property

Current URL

currentUrl shows browser URL when request was made

Reset URL

Click "Change URL", then click other buttons - currentUrl will show new URL

History Restoration

Click button to push URL, then browser back - isForHistoryRestoration = true

hx-history="false" disables htmx cache, forcing server request on back

Response
Click any button to see the response
HtmxRequest Properties

Values from request headers - updates in real-time

Property Value
isHtmx false
isBoosted false
currentUrl (null)
target (null)
trigger (null)
triggerName (null)
prompt (null)
isForHistoryRestoration false
Source Code View on GitHub
<?php

declare(strict_types=1);

namespace App\Controller;

use Mdxpl\HtmxBundle\Attribute\HtmxOnly;
use Mdxpl\HtmxBundle\Request\HtmxRequest;
use Mdxpl\HtmxBundle\Response\HtmxResponse;
use Mdxpl\HtmxBundle\Response\HtmxResponseBuilder;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Attribute\Route;

#[Route('/request-showcase')]
final class RequestShowcaseController extends AbstractController
{
    #[Route('', name: 'app_request_showcase')]
    public function index(HtmxRequest $htmx): HtmxResponse
    {
        return HtmxResponseBuilder::create($htmx->isHtmx)
            ->success()
            ->view('request_showcase.html.twig', [
                'htmx' => $htmx,
            ])
            ->build();
    }

    /**
     * Endpoint for testing HtmxRequest properties
     */
    #[Route('/request', name: 'app_request_showcase_request')]
    #[HtmxOnly]
    public function request(HtmxRequest $htmx): HtmxResponse
    {
        return HtmxResponseBuilder::create($htmx->isHtmx)
            ->success()
            ->viewBlock('request_showcase.html.twig', 'actionResult', ['htmx' => $htmx])
            ->viewBlock('request_showcase.html.twig', 'requestInfoOob', ['htmx' => $htmx])
            ->build();
    }

    /**
     * Endpoint that triggers a prompt dialog - demonstrates prompt property
     */
    #[Route('/prompt', name: 'app_request_showcase_prompt')]
    #[HtmxOnly]
    public function requestPrompt(HtmxRequest $htmx): HtmxResponse
    {
        $promptValue = $htmx->prompt ?? '(no prompt provided)';

        return HtmxResponseBuilder::create($htmx->isHtmx)
            ->success()
            ->viewBlock('request_showcase.html.twig', 'promptResult', ['htmx' => $htmx, 'promptValue' => $promptValue])
            ->viewBlock('request_showcase.html.twig', 'requestInfoOob', ['htmx' => $htmx])
            ->build();
    }

    /**
     * Demonstrates replaceUrl - changes currentUrl for subsequent requests
     */
    #[Route('/url', name: 'app_request_showcase_url')]
    #[HtmxOnly]
    public function url(HtmxRequest $htmx): HtmxResponse
    {
        $newUrl = '/request-showcase?demo=url&t=' . time();

        return HtmxResponseBuilder::create($htmx->isHtmx)
            ->success()
            ->viewBlock('request_showcase.html.twig', 'urlResult', ['htmx' => $htmx, 'newUrl' => $newUrl])
            ->viewBlock('request_showcase.html.twig', 'requestInfoOob', ['htmx' => $htmx])
            ->replaceUrl($newUrl)
            ->build();
    }

    /**
     * Demonstrates pushUrl for history restoration test
     */
    #[Route('/history', name: 'app_request_showcase_history')]
    #[HtmxOnly]
    public function history(HtmxRequest $htmx): HtmxResponse
    {
        $newUrl = '/request-showcase?history=' . time();

        return HtmxResponseBuilder::create($htmx->isHtmx)
            ->success()
            ->viewBlock('request_showcase.html.twig', 'historyResult', ['htmx' => $htmx, 'newUrl' => $newUrl])
            ->viewBlock('request_showcase.html.twig', 'requestInfoOob', ['htmx' => $htmx])
            ->pushUrl($newUrl)
            ->build();
    }

    /**
     * Demonstrates boosted request
     */
    #[Route('/boosted', name: 'app_request_showcase_boosted', methods: ['GET', 'POST'])]
    public function boosted(HtmxRequest $htmx): HtmxResponse|RedirectResponse
    {
        if (!$htmx->isHtmx) {
            return $this->redirectToRoute('app_request_showcase');
        }

        return HtmxResponseBuilder::create($htmx->isHtmx)
            ->success()
            ->viewBlock('request_showcase.html.twig', 'boostedResult', ['htmx' => $htmx])
            ->viewBlock('request_showcase.html.twig', 'requestInfoOob', ['htmx' => $htmx])
            ->build();
    }
}
{% block requestInfo %}
<figure>
    <table>
        <thead>
            <tr>
                <th>Property</th>
                <th>Value</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td class="prop-name">isHtmx</td>
                <td><span class="prop-badge {{ htmx.isHtmx ? 'prop-true' : 'prop-false' }}">{{ htmx.isHtmx ? 'true' : 'false' }}</span></td>
            </tr>
            <tr>
                <td class="prop-name">isBoosted</td>
                <td><span class="prop-badge {{ htmx.isBoosted ? 'prop-true' : 'prop-false' }}">{{ htmx.isBoosted ? 'true' : 'false' }}</span></td>
            </tr>
            <tr>
                <td class="prop-name">currentUrl</td>
                <td style="font-family: monospace; font-size: 0.75rem; word-break: break-all;">{{ htmx.currentUrl ?? '(null)' }}</td>
            </tr>
            <tr>
                <td class="prop-name">target</td>
                <td style="font-family: monospace; font-size: 0.75rem;">{{ htmx.target ?? '(null)' }}</td>
            </tr>
            <tr>
                <td class="prop-name">trigger</td>
                <td style="font-family: monospace; font-size: 0.75rem;">{{ htmx.trigger ?? '(null)' }}</td>
            </tr>
            <tr>
                <td class="prop-name">triggerName</td>
                <td style="font-family: monospace; font-size: 0.75rem;">{{ htmx.triggerName ?? '(null)' }}</td>
            </tr>
            <tr>
                <td class="prop-name">prompt</td>
                <td style="font-family: monospace; font-size: 0.75rem;">{{ htmx.prompt ?? '(null)' }}</td>
            </tr>
            <tr>
                <td class="prop-name">isForHistoryRestoration</td>
                <td><span class="prop-badge {{ htmx.isForHistoryRestoration ? 'prop-true' : 'prop-false' }}">{{ htmx.isForHistoryRestoration ? 'true' : 'false' }}</span></td>
            </tr>
        </tbody>
    </table>
</figure>
{% endblock %}

{% block requestInfoOob %}
<div id="request-info" hx-swap-oob="true">
    {{ block('requestInfo') }}
</div>
{% endblock %}

{% block actionResult %}
<div style="color: var(--pico-ins-color);">
    <strong>Request received!</strong>
    trigger={{ htmx.trigger ?? 'null' }}, triggerName={{ htmx.triggerName ?? 'null' }}, target={{ htmx.target ?? 'null' }}
</div>
{% endblock %}

{% block promptResult %}
<div style="color: var(--pico-ins-color);">
    <strong>Prompt value:</strong> {{ promptValue }}
</div>
{% endblock %}

{% block urlResult %}
<div style="color: var(--pico-del-color);">
    <strong>URL replaced:</strong> {{ newUrl }}<br>
    <small>Click other buttons - currentUrl will show this new URL</small>
</div>
{% endblock %}

{% block historyResult %}
<div style="color: var(--pico-primary);">
    <strong>URL pushed:</strong> {{ newUrl }}<br>
    <small>Now click browser back to see isForHistoryRestoration = true</small>
</div>
{% endblock %}

{% block boostedResult %}
<div id="boosted-response" style="color: var(--pico-ins-color);">
    <strong>Boosted request received!</strong> isBoosted = {{ htmx.isBoosted ? 'true' : 'false' }}
</div>
{% endblock %}