nabeghe / nohtml
Say no to HTML — write views in PHP with a clean, simple DSL.
Requires
- php: >=7.4
Requires (Dev)
- phpunit/phpunit: ^9.0
This package is auto-updated.
Last update: 2025-06-14 22:20:35 UTC
README
Say no to HTML — write views in PHP with a clean, simple DSL.
There are many PHP-based DSLs for HTML out there but NoHTML takes a different route in both behavior and structure.
At its core, there's a class where each method corresponds to an HTML element.
These methods can be used in both static and instance contexts.
Each method accepts a variadic list of arguments representing the element’s children.
The return value of every method is an instance of the Nabeghe\NoHtml\Nodes\Element
class,
whose methods map to the element's attributes, following a Fluent Builder pattern.
🫡 Usage
🚀 Installation
You can install the package via composer:
composer require nabeghe/nohtml
Example
In the example below, the NoHtml class is made available via an alias named N
.
You can also access the shared instance using the nohtml() function;
however, the below code proceeds in a static manner.
In this code, an HTML element is created right from the start, and then the rest of the page is built around it.
use Nabeghe\NoHtml\NoHtml as N; require 'vendor/autoload.php'; $app = N::html( N::head( N::meta()->charset('utf-8'), N::meta()->name('viewport')->charset('width=device-width, initial-scale=1.0'), N::title('No HTML for PHP by Nabeghe'), N::script()->src('https://cdn.tailwindcss.com'), ), N::body( N::header( N::h1('No HTML for PHP by Nabeghe')->class('text-3xl font-bold'), N::nav( N::a('Intro')->href('#intro')->class('over:underline'), N::a('Table')->href('#table')->class('hover:underline'), N::a('Form')->href('#form')->class('hover:underline'), )->class('mt-2 space-x-4'), )->class('bg-gray-900 text-white py-6 text-center'), N::main( N::section( N::h2('Introduction')->class('text-2xl font-semibold mb-4'), N::p(<<<EOD There are many PHP-based DSLs for HTML out there but **NoHTML** takes a different route in both behavior and structure. At its core, there's a class where each method corresponds to an HTML element. These methods can be used in both static and instance contexts. Each method accepts a variadic list of arguments representing the element’s children. The return value of every method is an instance of the `Nabeghe\NoHtml\Element` class, whose methods map to the element's attributes, following a **Fluent Builder** pattern. EOD )->class('mb-4'), N::p('This items generated by callback'), N::ul([Items::class, 'generate'])->data('template', 'Item {n}')->class('list-disc list-inside mb-4'), )->id('intro'), N::section( N::h2('Details')->class('text-2xl font-semibold mb-4'), N::div( N::table( N::tr( N::td('Github')->class([Css::class, 'tableCell']), N::td( N::a('https://github.com/nabeghe/nohtml-php')->href('https://github.com/nabeghe/nohtml-php')->title('No HTML'), )->class([Css::class, 'tableCell']), )->class([Css::class, 'tableRow']), N::tr( N::td('License')->class([Css::class, 'tableCell']), N::td('MIT')->class([Css::class, 'tableCell']), )->class([Css::class, 'tableRow']), )->class('min-w-full border border-gray-300 rounded-md'), )->class('overflow-x-auto'), )->id('table'), N::section( N::h2('Contact Us')->class('text-2xl font-semibold mb-4'), N::form( N::div( N::label('Name:')->for('name')->class('block font-medium'), N::input()->type('text')->id('name')->name('name')->placeholder('Enter your name')->class('w-full border border-gray-300 rounded px-3 py-2'), ), N::div( N::label('Email:')->for('email')->class('block font-medium'), N::input()->type('text')->id('email')->name('email')->placeholder('Your email')->class('w-full border border-gray-300 rounded px-3 py-2'), ), N::div( N::label('Nessage:')->for('message')->class('block font-medium'), N::textarea()->id('message')->name('message')->placeholder('Type your message here...')->class('w-full border border-gray-300 rounded px-3 py-2'), ), N::div( N::input()->type('checkbox')->id('agree')->name('agree')->placeholder('Your email')->checked(true)->class('h-4 w-4 text-blue-600 rounded'), N::label('I agree to the terms')->for('agree')->class('select-none'), )->class('flex items-center space-x-2'), N::button('Submit')->type('submit')->class('bg-blue-600 text-white rounded px-5 py-2 hover:bg-blue-700 transition'), )->action('#')->method('post')->class('space-y-4'), )->id('form'), )->class('max-w-4xl mx-auto p-6 bg-white mt-6 rounded-lg shadow-md space-y-10'), N::footer( new Unescapable('© No HTML - Built with ❤️ by <a href="https://elatel.ir">https://elatel.ir</a>'), )->class('bg-gray-900 text-white text-center py-6 mt-10') ), )->lang('en'); echo '<!DOCTYPE html>'; echo $app;
You can define a child element or an attribute value as a callable, so the actual value can be retrieved at the appropriate time.
Children can be plain strings or anything else — they will be converted to strings.
If they are plain strings, they will be escaped, unless the Nabeghe\NoHtml\Nodes\Unescapable
class is used.
Also, a callback defined for children can return a string, an element, or a list of elements.
If it returns a string, that string will not be escaped.
As for attributes, a callback can return the value of the attribute. If the value is null, that attribute won’t be set at all. So, if you want an empty value, use an empty string — null means the attribute doesn't exist. Also, if an attribute is set to true or false, no value will be assigned — true simply means the attribute will be present, and false means it won't be there.
Notice: Setting children for elements like <img>
or <br>
has no effect.
Callbacks
The class related to the callbacks used in the example above:
class Css { public static function tableRow(string $name, El $el) { return 'hover:bg-gray-100'; } public static function tableCell(string $name, El $el) { return 'border border-gray-300 px-4 py-2'; } }
class Items { public static function generate(El $el) { $template = $el->data('template'); $items = []; for ($i = 0; $i < 10; $i++) { $n = $i + 1; if ($n < 10) { $n = '0'.$n; } $items[] = N::li(str_replace('{n}', "$n", $template)); } return $items; } }
📖 License
Copyright (c) 2025 https://elatel.ir
Licensed under the MIT license, see LICENSE.md for details.