Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Hypertext Laravel Package

stevebauman/hypertext

Hypertext is a Laravel package for building HTML with PHP objects. Define reusable components as classes, compose them with slots and attributes, and render clean markup without template files—an object-oriented alternative to Blade for dynamic UI.

Deep Wiki
Context7

Getting Started

  1. Install the package via Composer:

    composer require stevebauman/hypertext
    

    Laravel's package discovery registers the service provider automatically. If your application does not use discovery, register the provider manually in config/app.php.

  2. Look first at:

    • The package README for the current fluent API.
    • src/Hypertext.php for the entry point.
    • src/Element.php (or equivalent) for attribute and child behavior.
    • The package tests for exact rendering and escaping expectations.

    The API is deliberately small: create an element, add attributes, add children or text, then render it. The snippets below use the package's fluent shape (make(), element methods, attribute(), text(), children(), render()); confirm exact method names against the README for your pinned version.

  3. First use case: replace a messy Blade partial with a reusable PHP component. For example, a button:

    use Stevebauman\Hypertext\Hypertext;
    
    $button = Hypertext::make()
        ->a()
        ->attribute('href', route('posts.show', $post))
        ->attribute('class', 'btn btn-primary')
        ->text($post->title)
        ->render();
    

    Then in Blade:

    {!! $button !!}
    

    Once the basic element works, move the construction into a dedicated class or Blade component so the markup is defined once and reused everywhere.

Implementation Patterns

Compose elements as small, reusable objects

Treat Hypertext components the same way you treat Blade components: one class per UI concept.

final class Alert
{
    public function __construct(
        private string $message,
        private string $type = 'info',
    ) {}

    public function render(): string
    {
        return Hypertext::make()
            ->div()
            ->attribute('class', "alert alert-{$this->type}")
            ->attribute('role', 'alert')
            ->text($this->message)
            ->render();
    }
}

Usage:

{!! (new Alert($message, 'danger'))->render() !!}

This keeps conditionals, attributes, and escaping rules in one testable place instead of scattering them across Blade templates.

Build nested structures with children

Nesting is where the object API becomes clearer than string concatenation.

$card = Hypertext::make()
    ->div()
    ->attribute('class', 'card')
    ->children([
        Hypertext::make()
            ->div()
            ->attribute('class', 'card-body')
            ->children([
                Hypertext::make()->h5()->attribute('class', 'card-title')->text($title),
                Hypertext::make()->p()->attribute('class', 'card-text')->text($body),
            ]),
    ]);

For collections, map your models into child elements and pass the resulting array to children():

$items = $posts->map(
    fn ($post) => Hypertext::make()
        ->li()
        ->attribute('class', 'list-group-item')
        ->text($post->title)
)->all();

$list = Hypertext::make()
    ->ul()
    ->attribute('class', 'list-group')
    ->children($items);

Handle conditional attributes explicitly

Use null or false to omit optional attributes, and true for boolean attributes, depending on the package's rendering rules.

$input = Hypertext::make()
    ->input()
    ->attribute('type', 'text')
    ->attribute('name', 'email')
    ->attribute('value', old('email'))
    ->attribute('required', $required ? true : null)
    ->attribute('disabled', $disabled ? true : null);

For more complex conditions, use a when()-style helper if available, or build the attribute array before constructing the element:

$attributes = [
    'class' => 'form-control',
    'id' => 'email',
];

if ($hasError) {
    $attributes['class'] .= ' is-invalid';
    $attributes['aria-describedby'] = 'email-error';
}

Then pass them through the package's bulk attribute method if one exists, or loop over them:

$element = Hypertext::make()->input();

foreach ($attributes as $name => $value) {
    $element->attribute($name, $value);
}

Integrate with Blade components

If your Hypertext element implements Laravel's Htmlable, return it directly from a Blade component's render() method and use normal Blade echoing:

public function render(): Htmlable
{
    return Hypertext::make()
        ->button()
        ->attribute('type', 'submit')
        ->attribute('class', 'btn btn-success')
        ->text($this->label);
}

If your component returns a plain string, Blade's {{ }} will escape the markup. In that case either wrap the result in Illuminate\Support\HtmlString or intentionally use {!! !!} for trusted, self-generated markup.

Test the rendered output

Hypertext makes markup assertions straightforward because you can render a component in isolation.

public function test_button_renders_expected_markup(): void
{
    $button = new Button('Save', '/save', 'primary');

    $html = $button->render();

    $this->assertStringContainsString('href="/save"', $html);
    $this->assertStringContainsString('class="btn btn-primary"', $html);
    $this->assertStringContainsString('Save', $html);
}

For Laravel response tests, assertSee() escapes by default. If you are asserting raw generated markup, pass false:

$response->assertSee('<a href="/save"', false);

Gotchas and Tips

  • Do not pre-escape text. If Hypertext automatically escapes text content, calling e() or htmlspecialchars() before passing it in will double-escape output. Pass raw model values and let the package handle escaping.
  • Only bypass escaping deliberately. If you need to inject trusted HTML markup, use the package's raw/unescaped text mechanism if provided, or render a child element. Avoid concatenating raw HTML strings into Hypertext output.
  • Attribute values are not a security boundary. Automatic attribute escaping protects against malformed markup, but it does not sanitize URLs or JavaScript event handlers. Validate user-supplied URLs and avoid passing user-controlled attribute names.
  • Know whether you have a string or an object. If the builder implements Htmlable, {{ $element }} is safe and correct. If it returns a plain string, {{ $element }} will display escaped tags. Standardize on one convention in your codebase.
  • Watch conditional attribute semantics. Confirm how false, null, true, and empty strings render for boolean attributes. Behavior can differ between disabled, checked, selected, and normal string attributes.
  • Keep components focused. Hypertext is most valuable for reusable, dynamic UI fragments: buttons, alerts, form controls, nav items, tables cells. For large static marketing pages, plain Blade is often easier to read and maintain.
  • Do not mix concatenation and the builder. Building half an element as a string and half as an object usually produces confusing whitespace and escaping behavior. Build the whole fragment with Hypertext or keep it entirely in Blade.
  • Expect minimal whitespace formatting. Programmatically built HTML is often compact and not pretty-printed. Do not assert on exact whitespace in tests; assert on structure, attributes, and text instead.
  • Cache expensive or repeated fragments. Building many nested objects in a loop adds overhead compared with a simple Blade loop. If a navigation menu or table is rendered on every request, consider caching the rendered HTML fragment.
  • Pin the version. The package's last release is from October 2024. Method names and fluent helpers can vary between versions, so lock the version in composer.json and check the README for that tag rather than assuming the latest docs match.
  • Debug by rendering early. Call ->render() or dd($element) at each step while composing. Inspect the final string for missing attributes, unexpected escaping, and unclosed children.
  • Use extension points sparingly. If the package exposes macros, custom elements, or Blade integration, prefer those over forking. If you truly need custom rendering, wrap Hypertext inside your own component class so the rest of the application remains decoupled from its API.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource