carlos-andres/nova-html-field

A Laravel Nova field for rendering HTML content with XSS protection

Maintainers

Package info

github.com/carlos-andres/nova-html-field

pkg:composer/carlos-andres/nova-html-field

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-02-01 19:22 UTC

This package is auto-updated.

Last update: 2026-03-29 19:52:31 UTC


README

Latest Version on Packagist Total Downloads

A Laravel Nova 4/5 field for rendering HTML content with built-in XSS protection via HTMLPurifier.

Features

  • XSS Protection - HTMLPurifier sanitization enabled by default
  • Dynamic Content - Resolve HTML from model attributes or closures
  • Inline Styles - Full support for inline CSS styling
  • View Control - Standard Nova visibility methods
  • Conditional Display - Show/hide based on request conditions

Requirements

  • PHP 8.1+
  • Laravel 10+
  • Nova 4+ or Nova 5+

Installation

composer require carlos-andres/nova-html-field

No build step required - works out of the box.

Quick Start

use Vendor\NovaHtmlField\HtmlField;

// Static content
HtmlField::make('Notice')
    ->content('<p style="color: #059669;">Settings saved successfully</p>');

// Dynamic from model
HtmlField::make('Preview')
    ->html(fn ($model) => '<strong>'.e($model->title).'</strong>');

// From model attribute
HtmlField::make('Description', 'html_content');

Usage

Static HTML with content()

HtmlField::make('Info Banner')
    ->content('
        <div style="background: #dbeafe; border-radius: 8px; padding: 16px;">
            <h3 style="margin: 0 0 4px 0; font-weight: 600; color: #1e40af;">
                Configuration
            </h3>
            <p style="margin: 0; font-size: 14px; color: #3b82f6;">
                Manage your settings below
            </p>
        </div>
    ')
    ->onlyOnForms();

Dynamic Content with html()

// Status badge that changes based on model state
HtmlField::make('Status')
    ->html(fn ($model) => '
        <span style="
            background: '.($model->is_active ? '#d1fae5' : '#fee2e2').';
            color: '.($model->is_active ? '#065f46' : '#991b1b').';
            padding: 4px 12px;
            border-radius: 9999px;
            font-size: 12px;
            font-weight: 500;
        ">
            '.($model->is_active ? 'Active' : 'Inactive').'
        </span>
    ')
    ->onlyOnIndex();

// Image preview
HtmlField::make('Thumbnail')
    ->html(fn ($model) => $model->image_url
        ? '<img src="'.e($model->image_url).'" style="max-width: 100px; border-radius: 4px;">'
        : '<span style="color: #9ca3af;">No image</span>'
    );

From Model Attribute

// Direct attribute (sanitized automatically)
HtmlField::make('Body', 'html_content');

// With transform callback
HtmlField::make('Formatted', 'raw_content', function ($value) {
    return '<div style="white-space: pre-wrap;">'.e($value).'</div>';
});

View Visibility

HtmlField::make('Details')
    ->content('<p>Only visible on detail view</p>')
    ->onlyOnDetail();

HtmlField::make('Summary')
    ->html(fn ($m) => $m->summary_html)
    ->showOnIndex()
    ->hideFromDetail();

HtmlField::make('Form Help')
    ->content('<p style="color: #6b7280;">Fill in all required fields</p>')
    ->onlyOnForms();

Conditional Rendering

// Show only for admins
HtmlField::make('Admin Panel')
    ->content('<div>Admin-only content</div>')
    ->when(fn ($request) => $request->user()->isAdmin());

// Hide for admins
HtmlField::make('User Notice')
    ->content('<p>Contact admin for changes</p>')
    ->unless(fn ($request) => $request->user()->isAdmin());

Styling Guide

Use Inline Styles (Recommended)

Inline styles are the most reliable way to style HtmlField content:

HtmlField::make('Alert')
    ->content('
        <div style="
            background: #fef3c7;
            border: 1px solid #f59e0b;
            border-radius: 8px;
            padding: 12px 16px;
            display: flex;
            align-items: center;
            gap: 8px;
        ">
            <span style="font-size: 18px;">Warning</span>
            <span style="color: #92400e;">Please review before saving</span>
        </div>
    ');

Tailwind CSS Limitations

Tailwind utility classes (e.g., bg-blue-500, p-4, rounded-lg) will not render unless they are already included in Nova's compiled CSS bundle. Nova only includes the Tailwind classes it uses internally.

// This may NOT work (classes might not exist in Nova's CSS)
HtmlField::make('Card')
    ->content('<div class="bg-blue-100 p-4 rounded-lg">Hello</div>');

// This WILL work (inline styles always render)
HtmlField::make('Card')
    ->content('<div style="background: #dbeafe; padding: 16px; border-radius: 8px;">Hello</div>');

Icons and Emojis

HTMLPurifier strips SVG elements by default. Use emoji or Unicode symbols instead:

// Using emoji (works)
HtmlField::make('Files')
    ->content('<h3>📁 Files Section</h3>');

HtmlField::make('Images')
    ->content('<h3>🖼️ Images Section</h3>');

// SVG will be stripped (won't work without disabling sanitization)
HtmlField::make('Files')
    ->content('<svg>...</svg> Files Section');

Security

Default Protection

All HTML is sanitized using HTMLPurifier:

Threat Protection
<script> tags Removed
Event handlers (onclick, onerror) Removed
javascript: URLs Blocked
<style>, <object>, <embed> Removed
data: URLs in images Blocked
Safe HTML elements Preserved
Inline styles Preserved

Best Practices

Always escape dynamic content:

// Good - escaped
HtmlField::make('Title')
    ->html(fn ($m) => '<strong>'.e($m->title).'</strong>');

// Bad - XSS vulnerable
HtmlField::make('Title')
    ->html(fn ($m) => '<strong>'.$m->title.'</strong>');

Disable Sanitization (Trusted Content Only)

// Only for content you completely control
HtmlField::make('Trusted HTML')
    ->html(fn ($m) => $m->trusted_html)
    ->withoutSanitization();

Custom Purifier Configuration

// Restrict allowed elements
HtmlField::make('Simple')
    ->html(fn ($m) => $m->html)
    ->purifierConfig([
        'HTML.Allowed' => 'p,b,i,a[href]',
    ]);

// Allow target="_blank" on links
HtmlField::make('Links')
    ->html(fn ($m) => $m->html)
    ->purifierConfig([
        'Attr.AllowedFrameTargets' => ['_blank'],
    ]);

See HTMLPurifier docs for all options.

API Reference

Method Description
content(string $html) Set static HTML content
html(Closure $callback) Set HTML via closure (receives model)
withoutSanitization() Disable HTMLPurifier (use with caution)
purifierConfig(array $config) Custom HTMLPurifier settings
when(Closure $callback) Show when condition is true
unless(Closure $callback) Show unless condition is true

Inherited Nova Methods

  • onlyOnIndex(), onlyOnDetail(), onlyOnForms()
  • showOnIndex(), showOnDetail(), showOnCreating(), showOnUpdating()
  • hideFromIndex(), hideFromDetail(), hideWhenCreating(), hideWhenUpdating()
  • exceptOnForms()
  • canSee(Closure $callback)
  • fullWidth()
  • help(string $text)

Testing

composer test

Note: Tests require Nova classes. Run from within a Laravel project that has Nova installed, or the tests will fail with "Class not found" errors.

Changelog

future implementation.

License

MIT License. See LICENSE for details.