Skip to content

[13.x] Omit empty class/style attribute from @class and @style directives#59751

Open
damianlewis wants to merge 1 commit intolaravel:13.xfrom
damianlewis:fix/blade-class-style-empty-attribute
Open

[13.x] Omit empty class/style attribute from @class and @style directives#59751
damianlewis wants to merge 1 commit intolaravel:13.xfrom
damianlewis:fix/blade-class-style-empty-attribute

Conversation

@damianlewis
Copy link
Copy Markdown

@damianlewis damianlewis commented Apr 17, 2026

Summary

@class([...]) and @style([...]) currently render class="" / style="" on the target element when all conditions evaluate to false (or the input array is empty). The compiler traits hardcode the attribute wrapper around the result of Arr::toCssClasses / Arr::toCssStyles, so an empty string from the helper still produces an empty-valued attribute in the rendered HTML.

This PR changes both compilers to emit the attribute only when the helper returns a non-empty string.

Before / after

Given:

<span @class(['highlight' => $required])></span>

When $required is false:

Rendered HTML
Before <span class=""></span>
After <span></span>

When $required is true, output is unchanged: <span class="highlight"></span>. Same shape for @style.

Why this is a bug, not just cosmetic

The directive's API contract is "apply classes/styles conditionally." Emitting an empty attribute when no classes/styles apply leaks the compiler's internal wrapping into HTML, where downstream tooling observes it differently from the absent-attribute state:

  • CSS [class] / [style] attribute selectors unexpectedly match elements with no styling intent
  • element.hasAttribute('class') returns true and getAttribute('class') returns "", rather than false / null
  • HTML-equality snapshot assertions include noise attributes unrelated to styling intent
  • Raw server-rendered HTML bytes carry the attribute for no semantic gain

Scope

Only the two compiler traits (CompilesClasses, CompilesStyles) are changed. ComponentAttributeBag::__toString() is intentionally untouched because boolean HTML attributes (disabled, checked, required, etc.) flow through it and legitimately render with empty values per the HTML spec. class and style are value attributes (not boolean), so their dedicated compiler traits are the correct place for this fix.

Related work

This PR complements #57467 ("Improved empty and whitespace-only string handling in Blade component attributes"), which takes a parallel approach inside ComponentAttributeBag. Both changes move toward Vue-aligned attribute-emission behavior.

Related: #57463, #57235.

Why this doesn't break typical usage

The observable change only occurs when every class in a @class([...]) or @style([...]) call is conditional and all conditions evaluate to false. Templates with at least one unconditional class — the common pattern like @class(['base', 'active' => $cond]) — are entirely unaffected.

In the narrow case that is affected, the rendered output is semantically identical to the previous behavior: CSS rules matching specific class names still don't match (no classes are present either way); visual layout is identical. The only observable difference is attribute presence for tooling that specifically inspects it (e.g. CSS [class] attribute selectors, JavaScript hasAttribute checks). This is uncommon enough that no existing tests in the framework exercised it, and the new behavior aligns with the Vue-style attribute emission referenced on #57467.

Tests

  • BladeClassTest: updated the existing compilation assertion and added four runtime tests (empty array, all-false, mixed true/false, base class + all-false).
  • BladeStyleTest: parallel coverage for @style.
  • BladePhpStatementsTest: updated nine existing assertions whose expected strings referenced the old compiled output. No semantic change to those tests.

Runtime tests use the ob_start() + eval('?>'.$compiled) pattern already established in BladeBoolTest, BladeComponentsTest, and BladeEchoHandlerTest.

@github-actions
Copy link
Copy Markdown

Thanks for submitting a PR!

Note that draft PRs are not reviewed. If you would like a review, please mark your pull request as ready for review in the GitHub user interface.

Pull requests that are abandoned in draft may be closed due to inactivity.

@damianlewis damianlewis force-pushed the fix/blade-class-style-empty-attribute branch from 59ce410 to 0b26483 Compare April 17, 2026 14:59
@damianlewis damianlewis changed the title Omit empty class/style attribute from @class and @style directives [13.x] Omit empty class/style attribute from @class and @style directives Apr 17, 2026
@damianlewis damianlewis changed the base branch from master to 13.x April 17, 2026 15:00
@damianlewis damianlewis force-pushed the fix/blade-class-style-empty-attribute branch from 0b26483 to 13fceb7 Compare April 17, 2026 15:15
@damianlewis damianlewis force-pushed the fix/blade-class-style-empty-attribute branch from 13fceb7 to 6f0ec4e Compare April 17, 2026 15:28
@damianlewis damianlewis marked this pull request as ready for review April 17, 2026 15:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant