Blade audit
Detect hardcoded text without @text(), duplicate CSS classes, unused Blade components, and accessibility issues.From its SKILL.md
npx -y skills add edulazaro/laraclaude --skill blade-auditAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 12 stars12 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
13.1 KB, as published. Nobody here has run it
Subcommands
| Subcommand | Description |
|---|---|
report | Scan all Blade files and report issues without making changes |
fix | Scan all Blade files, report issues, and auto-fix what is safe |
fix --dry-run | Show what fixes would be applied without writing changes |
<file-path> | Scan a single file and report issues |
fix <file-path> | Scan a single file and auto-fix issues |
Overview
This skill audits Blade templates for localization issues, accessibility problems, Blade syntax errors, and code quality concerns. It enforces the project's strict requirement that ALL user-visible text must use @text() or text() helpers with English defaults.
Instructions
Step 1: Determine scope
Parse the argument:
- No argument or
report: Scan all.blade.phpfiles underresources/views/. fix(no path): Same scope, apply safe fixes.fix --dry-run: Show proposed fixes without applying.<file-path>: Scan only the specified file.fix <file-path>: Scan and fix only the specified file.
Use Glob to find files:
resources/views/**/*.blade.php
Step 2: Run each detection rule
Rule 1: Hardcoded user-visible text not wrapped in @text() or text()
This is the most critical rule. ALL user-visible text must be translated.
What counts as user-visible text:
- Text content between HTML tags:
<span>Some text</span> - Button labels:
<button>Submit</button> - Placeholder attributes:
placeholder="Enter name" - Title attributes:
title="Click here" - Label text:
<label>Name</label> - Alt text on images:
alt="Profile photo" - Heading content:
<h1>Dashboard</h1> - Link text:
<a href="...">Click here</a> - Option text:
<option>Select one</option> - Aria labels:
aria-label="Close"
What is NOT user-visible (ignore these):
- HTML attributes:
class="...",id="...",type="...",name="...",wire:model="..." - Blade directives:
@if,@foreach,@extends,@section - PHP code blocks:
<?php ... ?> - JavaScript code inside
<script>tags - CSS inside
<style>tags - Route names:
route('...') - Variable interpolation that's already dynamic:
{{ $user->name }} - Comments:
{{-- ... --}} - Wire/Alpine attributes:
wire:click="...",@click="...",x-on:click="..." - Data attributes:
data-*="..." - Single characters used as separators:
/,|,-,: - Numbers standing alone
- HTML entities:
,©
Detection approach:
- Parse each line of the blade file
- Skip lines that are entirely PHP, comments, Blade directives, script, or style blocks
- For remaining lines, look for text content between HTML tags that is:
- Longer than 1 character (ignore single chars)
- Not already wrapped in
@text(),text(),__(), or{{ }} - Not purely whitespace
- Not an HTML entity
- Also check
placeholder="...",title="...",alt="...", andaria-label="..."attributes for hardcoded strings
Report format:
[HARDCODED-TEXT] file.blade.php:45
<span>Send message</span>
Should be: <span>@text('send_message', 'Send message')</span>
Fix: Wrap text in @text() with an appropriate key following the naming conventions:
- 1-3 words: lowercase with underscores (
send_message,our_address) - Longer text: dot notation with section (
web.contact.description) - Strip punctuation from key name
- Default text in English
Rule 2: Non-English default text in @text() calls
The project requires ALL default text to be in English. Detect common Spanish words/patterns in @text() defaults.
Pattern to detect: @text('key', 'Non-English text') or text('key', 'Non-English text') where the default contains non-English words.
Common Spanish indicators to check:
- Articles:
el,la,los,las,un,una,unos,unas,del - Prepositions:
de,en,por,para,con,sin,sobre,entre,desde,hasta - Verbs:
es,son,esta,estan,tiene,hacer,crear,editar,eliminar,guardar,enviar,buscar,ver,agregar,actualizar,cerrar,abrir,seleccionar,confirmar - Common words:
nombre,direccion,telefono,correo,mensaje,precio,fecha,tipo,estado,nuevo,todos,ninguno,hola - Accented characters common in Spanish: words with
a,e,i,o,u+ accents in the context of otherwise Spanish text
Also detect other languages (French, German, Portuguese) by checking for non-ASCII characters in default text that suggest a non-English language.
Report format:
[NON-ENGLISH-DEFAULT] file.blade.php:23
@text('send_message', 'Enviar mensaje')
Default text appears to be Spanish. Should be: @text('send_message', 'Send message')
Fix: Replace the default text with an English translation. Use the key as a hint for what the English text should be.
Rule 3: Duplicate identical CSS class strings
Pattern to detect: Identical long class strings (10+ classes) that appear in 3+ files. These should be extracted to a Blade component or Tailwind @apply.
Build a map of class strings -> files. Report duplicates.
Report format:
[DUPLICATE-CLASSES] Found in 4 files
Class string: "px-4 py-2 text-sm font-medium text-white bg-pink-600 rounded-lg hover:bg-pink-700 focus:ring-4 focus:ring-pink-300"
Files:
- modals/create-user.blade.php:45
- modals/edit-user.blade.php:67
- modals/create-property.blade.php:89
- modals/edit-property.blade.php:34
Consider: Extract to a Blade component or use @apply in CSS
Fix: No auto-fix (requires architectural decision). Report only.
Rule 4: Unused Blade components
Pattern to detect: Component files in resources/views/components/ that are never referenced anywhere in the project.
- List all component files under
resources/views/components/ - For each component, derive its usage tag:
resources/views/components/modal.blade.php-><x-modalorx-modalresources/views/components/fields/text-input.blade.php-><x-fields.text-input
- Search ALL blade files for that component tag
- Report components with zero usages
Report format:
[UNUSED-COMPONENT] resources/views/components/old-button.blade.php
Component <x-old-button> is not used anywhere in the project.
Consider removing it.
Fix: No auto-fix (may be used dynamically or in packages). Report only.
Rule 5: Nested {{ }} inside {{ }}
Pattern to detect: Blade expressions containing nested {{ }} which is a syntax error.
Look for patterns like:
class="{{ $condition ? 'bg-{{ $color }}-600' : 'bg-gray-600' }}"
This is a known Blade limitation documented in the project's CLAUDE.md.
Report format:
[NESTED-INTERPOLATION] file.blade.php:67
class="{{ $tab === 'home' ? 'bg-{{ $color }}-600' : 'bg-gray-600' }}"
Fix: Define class strings in a @php block, then reference variables.
Fix: Extract the inner expressions to a @php block:
@php
$activeClass = "bg-{$color}-600";
@endphp
<!-- then use -->
class="{{ $tab === 'home' ? $activeClass : 'bg-gray-600' }}"
Rule 6: Missing alt attributes on images
Pattern to detect: <img> tags without an alt attribute. This is an accessibility violation (WCAG 2.1 Level A).
Look for <img tags that do not contain alt=" or alt=' or :alt=".
Report format:
[MISSING-ALT] file.blade.php:112
<img src="{{ $property->image }}" class="w-full h-48 object-cover">
Missing alt attribute. Add: alt="@text('property_image', 'Property image')"
Fix: Add an alt attribute with @text(). Infer a sensible description from context (nearby text, variable names, component purpose).
Rule 7: Forms without CSRF protection
Pattern to detect: <form tags that do not contain @csrf anywhere inside the form body.
Exclude:
- Forms with
wire:submit(Livewire handles CSRF automatically) - Forms with
method="GET"(CSRF not needed for GET) - Forms with
actionpointing to external URLs
Report format:
[MISSING-CSRF] file.blade.php:34
<form method="POST" action="{{ route('contact.store') }}">
Missing @csrf directive. Add @csrf inside the form.
Fix: Add @csrf as the first child inside the <form> tag.
Rule 8: Buttons without type attribute
Pattern to detect: <button> tags without a type attribute. Buttons default to type="submit" in HTML, which can cause unintended form submissions.
Exclude:
- Buttons that already have
type="submit",type="button", ortype="reset" - Buttons inside Livewire forms that intentionally submit
Report format:
[MISSING-BUTTON-TYPE] file.blade.php:78
<button @click="$dispatch('close-modal')" class="...">Cancel</button>
Missing type attribute. Defaults to "submit" which may cause unintended form submission.
Add: type="button"
Fix: Add type="button" for non-submit buttons (those with @click, wire:click, onclick, or that appear to be cancel/close/toggle buttons). Add type="submit" for buttons that should submit forms.
Rule 9: Hardcoded colors in group context
Pattern to detect: Hardcoded pink/brand colors in files that operate within the group/organization context (files under resources/views/group/, files that access $group or group_context).
Look for:
bg-pink-600,text-pink-600,border-pink-500,hover:bg-pink-700, etc.- In files that should use dynamic
$colorfrom$group->color_scheme
Report format:
[HARDCODED-COLOR] resources/views/group/pages/contact.blade.php:45
bg-pink-600 should be bg-{{ $color }}-600 in group context.
Groups support custom color schemes.
Fix: Replace hardcoded color with dynamic {{ $color }} variable. Ensure the $color variable is set from $group->color_scheme in a @php block at the top of the file.
Step 3: Generate report
Group findings by file, then by rule:
Severity levels:
- ERROR: Will cause runtime errors or broken functionality (nested interpolation, missing CSRF)
- WARNING: Violates project requirements (hardcoded text, non-English defaults, hardcoded colors)
- INFO: Best practices and accessibility (missing alt, button types, unused components, duplicates)
Output format:
## Blade Audit Report
### resources/views/livewire/properties/index.blade.php
- [WARNING] Line 45: Hardcoded text "Send message" not wrapped in @text()
- [WARNING] Line 23: Non-English default in @text('enviar', 'Enviar mensaje')
- [INFO] Line 112: <img> missing alt attribute
- [INFO] Line 78: <button> missing type attribute
### resources/views/group/pages/contact.blade.php
- [ERROR] Line 67: Nested {{ }} inside {{ }}
- [WARNING] Line 45: Hardcoded bg-pink-600 in group context
### Unused Components
- resources/views/components/old-alert.blade.php (x-old-alert)
- resources/views/components/deprecated-card.blade.php (x-deprecated-card)
### Duplicate Class Strings (3+ occurrences)
- "px-4 py-2 text-sm font-medium text-white bg-pink-600..." (found in 4 files)
---
**Summary**: 1 error, 3 warnings, 2 info across 2 files.
**Plus**: 2 unused components, 1 duplicate class string pattern.
Step 4: Apply fixes (if fix mode)
Safe auto-fixes (applied automatically):
- Wrap hardcoded text in
@text()with appropriate key and English default - Replace non-English defaults with English equivalents
- Fix nested
{{ }}by extracting to@phpblocks - Add
altattributes to images with@text()wrapped descriptions - Add
@csrfto forms missing it - Add
type="button"to non-submit buttons - Replace hardcoded colors with
{{ $color }}in group context
Unsafe fixes (report only):
- Duplicate class strings (requires component extraction decision)
- Unused components (may be used dynamically or planned for future use)
Generating @text() keys
When wrapping text in @text(), follow these key naming rules:
Short text (1-3 words):
Send->@text('send', 'Send')Our Address->@text('our_address', 'Our Address')Get in Touch->@text('get_in_touch', 'Get in Touch')
Longer text (sentences/descriptions):
- Determine the section from the file path:
resources/views/group/pages/contact.blade.php->web.contactresources/views/livewire/properties/index.blade.php->properties.indexresources/views/components/layouts/marketing.blade.php->web.layout
- Build key:
section.page.element@text('web.contact.heading', 'Contact Us')@text('properties.index.no_results', 'No properties found matching your criteria')
Punctuation rule: Keep punctuation OUTSIDE the translation:
- CORRECT:
@text('sending', 'Sending')... - WRONG:
@text('sending', 'Sending...') - CORRECT:
@text('message_sent', 'Message sent successfully')! - WRONG:
@text('message_sent', 'Message sent successfully!')
Before generating a key: Search existing @text() calls in the project to avoid creating duplicate keys for the same concept. Reuse existing keys when the meaning matches exactly.