TL;DR
- You can't put a Blade directive (
@if/@endif) inside a component tag's attribute list. Blade compiles component tags before directives, so the view won't compile β the page 500s. - Fix: branch around the whole component, or compute the attribute value in a plain PHP expression.
- The real lesson: this shipped unnoticed because the page sat behind a feature gate and no test ever rendered it. Touch a gated page β add a render test.
The trap
I wanted a button that only shows a confirm dialog when there's already a secret to rotate. So I reached for the obvious thing: drop an @if right into the attribute list.
<flux:button
variant="{{ $hasToken ? 'danger' : 'primary' }}"
wire:click="rotateToken"
@if($hasToken) wire:confirm="Rotate the secret? Existing integrations will break." @endif
>
{{ $hasToken ? 'Rotate Secret' : 'Generate Secret' }}
</flux:button>
Looks harmless. It isn't. The page throws a 500 with:
syntax error, unexpected token "endif"
Why it breaks
Blade doesn't evaluate a component tag the way your eyes read it. The compile order is the problem:
Blade source
|
v
[1] parse component tags <flux:button ...> -> PHP that builds the component
| (attributes captured as a literal list HERE)
v
[2] compile directives @if / @endif -> if(...) { }
By the time step 2 would turn @if/@endif into real PHP, step 1 has already swallowed everything between <flux:button and > as an attribute string. The directive never gets its closing half in a valid position, and the compiled view is broken PHP. Plain HTML tags tolerate this; component tags do not.
Three ways that actually work
| Approach | When to use |
|---|---|
Branch around the whole component with @if ... @else ... @endif
|
The conditional changes several attributes at once |
Compute the value in an expression: wire:confirm="{{ $hasToken ? '...' : '' }}"
|
One attribute, and an empty value is harmless |
| Bind a prop from the component's PHP class | The logic is reusable or non-trivial |
The branch-around version is what I shipped, because the confirm text and the variant both flip together:
@if($hasToken)
<flux:button variant="danger" wire:click="rotateToken"
wire:confirm="Rotate the secret? Existing integrations will break.">
Rotate Secret
</flux:button>
@else
<flux:button variant="primary" wire:click="rotateToken">
Generate Secret
</flux:button>
@endif
More markup, yes. But it compiles, and each branch reads as exactly one state.
The bigger miss: nothing rendered the page
Here's the part worth keeping. This bug didn't survive because it was subtle β it survived because the page lived behind a feature gate, so it was never hit in a test. A gate is a great way to ship a view that literally no one, including CI, ever renders.
The cheapest insurance is a render test. If a feature-gated page has one test that grants the feature and asserts a 200, this class of bug dies on the first run.
beforeEach(function () {
$this->user = User::factory()->withOrganization()->create();
$this->actingAs($this->user);
grantFeature($this->user->currentOrganization, 'api-access'); // helper
});
it('renders the settings page without a secret', function () {
$this->get(route('organization.api.index'))
->assertOk()
->assertSee('Generate Secret');
});
it('renders the settings page with a secret set', function () {
$this->user->currentOrganization->update(['api_secret' => 'existing']);
$this->get(route('organization.api.index'))
->assertOk()
->assertSee('Rotate Secret');
});
Two assertions, both branches of the button, done. While I was there I also added a test that the generated secret is shown once and stored encrypted at rest β but that's a separate story.
Takeaway
Component tags compile before directives, so keep directives out of their attribute lists β branch around the component or compute the value. And treat every feature-gated page as untested until proven otherwise: a one-line render test is the difference between catching this in CI and catching it in production.
United States
NORTH AMERICA
Related News

Mattress Firm Coupons: Save up to $600
4h ago
π I Built a Dropshipping Automation Pipeline β Here's What I Learned (and What I'd Do Differently)
11h ago
How I Cut My LLM API Bill by 40x: A Freelancer's Migration Story
11h ago
Cursor AI Review 2026: The AI-Native Code Editor
8h ago

Another Model Rewrote My Memories. Here's How I Caught It.
8h ago