diff --git a/README.md b/README.md
index 7dfbc3a..d7de893 100644
--- a/README.md
+++ b/README.md
@@ -1,48 +1,122 @@
# 8fold HTML Builder for PHP
-HTML Builder extends and is a web-specific implementation of
-[8fold XML Builder](https://github.com/8fold/php-xml-builder); similar to how
-HTML extends and is a specific implementation of XML.
+HTML Builder is a web-specific string compiler.
+
+Warning: Users of this library are responsible for sanitizing content.
+
+- [Installation](#installation)
+- [Usage](#usage)
+ - [`Element`](#element)
+ - [`EmptyElement`](#emptyelement)
+ - [`Document`](#document)
+ - [`Comment`](#comment)
+ - [Properties](#properties)
+- [Extending](#extending)
+- [Performance](#performance)
+- Migrating (`docs/`)
+ - [5.0 to 5.1](./docs/migrating-from-050000-050100.md)
+ - [4.x to 5.0](#migrating-from-4x-to-50)
+- [Details](#details)
+- [Other](#other)
## Installation
`composer require 8fold/php-html-builder`
-## Usage (basic)
+## Usage
-Warning: Users of this library are responsible for sanitizing content.
+HTML Builder quickly outputs HTML strings with consistent
+ordering of attributes for elements.
+
+The default order is in the [`Element`](https://github.com/8fold/php-html-builder/blob/main/src/Element.php)
+class. If you prefer a different ordering, you can extend and override the ‘Element’ class and ordering constant.
+
+### `Element`
+
+The fundamental building block for all output.
+
+```php
+use Eightfold\HtmlBuilder\Element;
+
+// non-empty element, using `create()`
+Element::create('p', 'Hello, World!')->props('id intro');
+```
-By default, HTML Builder can quickly output an HTML string with consistent
-ordering of attributes for elements. The default order can be found in the
-[`Element`](https://github.com/8fold/php-html-builder/blob/main/src/Element.php)
-class, which can be overridden, if you prefer a different ordering. This helps
-create and maintain a consistent HTML pattern. Further, this pattern can be
-changed without updating templates or other code.
+Output:
+```html
+
Hello, World!
+```
+
+Non-empty element using magic method:
+```php
+Element::p('Hello, World!')->props('id intro');
+```
+Output:
+```html
+
Hello, World!
+```
+
+Empty element using magic method:
```php
-use Eightfold\HTMLBuilder\Document;
-use Eightfold\HTMLBuilder\Element;
+Element::img()->omitEndTag()->props('src /path/to/image.png');
+```
+
+Output:
+```html
+
+```
+
+### `EmptyElement`
+
+The [`EmptyElement`](https://github.com/8fold/php-html-builder/blob/main/src/EmptyElement.php) class was introduced in the 5.1 release and is designed to mirror the input shorthand of HTML those elements compared to using the `Element` class to do the same.
+
+```php
+use Eightfold\HtmlBuilder\Element;
+use Eightfold\HtmlBuilder\EmptyElement;
+
+// empty element use Element
+Element::img()->omitEndTag()->props('src /path/to/image.png');
+```
+
+Output:
+```html
+
+```
+
+Using `EmptyElement`:
+```php
+EmptyElement::img('src /path/to/image.png');
+
+// output same as previous
+```
+
+### `Document`
+
+The [`Document`](https://github.com/8fold/php-html-builder/blob/main/src/Document.php) class gives you to generate a complete HTML document entirely in PHP.
+
+```php
+use Eightfold\HtmlBuilder\Document;
+use Eightfold\HtmlBuilder\Element;
+use Eightfold\HtmlBuilder\EmptyElement;
Document::create('title')
->head(
- Element::link()
- ->omitEndTag()->props('rel stylesheet', 'href style.css'),
+ EmptyElement::link('rel stylesheet', 'href style.css'),
Element::script()->props('src script.js')
)->body(
- Element::p('paragraph content')
- )
+ Element::p('Hello, World!')
+ );
```
Output:
```html
+
-title
paragraph content
-```
-
-Output (formatted):
+title
Hello, World!
-```html
+
@@ -57,253 +131,91 @@ Output (formatted):
```
-Further examples can be found in the
-[`tests`](https://github.com/8fold/php-html-builder/tree/main/tests) folder with
-an example extension.
+Further examples are in the [`tests`](https://github.com/8fold/php-html-builder/tree/main/tests) folder with an example extension.
-A note on properties: If a property is not in the ordered list, and the value of
-the property is the same as the name of the property, HTML Builder will presume
-it's a boolean value and will truncate the definition in the HTML output.
+### `Comment`
+
+There’s even a [`Comment`](https://github.com/8fold/php-html-builder/blob/main/src/Comment.php) component for adding inline HTML comments.
```php
-Element::input()->omitEndTag()->props("required required");
-```
+use Eightfold\HtmlBuilder\Document;
+use Eightfold\HtmlBuilder\Element;
+use Eightfold\HtmlBuilder\EmptyElement;
+use Eightfold\HtmlBuilder\Comment;
-Output:
+Document::create('title')
+ ->head(
+ EmptyElement::link('rel stylesheet', 'href style.css'),
+ Element::script()->props('src script.js')
+ )->body(
+ Comment::create('comment-based-injection-point'),
+ Element::p('Hello, World!')
+ );
+```
+Output (human-readable):
```html
-
+
+
+
+ title
+
+
+
+
+
+
+
paragraph content
+
+
```
-## Migrating from 4.x to 5.0
-
-This section highlights the changes to check when upgrading from 4.4.0. It
-links to the [form control guidance](#form-control-guidance) for details that
-apply to the new and existing APIs.
-
-### Required migration checks: markup and property targets
-
-Text-entry controls now render an outer `
` unconditionally. In 4.4.0,
-`TextInput`, `EmailInput`, and `Password` rendered their label and input as siblings without
-that container. Update CSS child/sibling selectors, flex or grid layouts, and
-JavaScript traversal that assumed the old structure.
-
-```html
-
-
+### Properties
-
-
-
-
-```
+`Element` arranges all properties that are not in the ordered list alphabetically after the ordered properties.
-Use each property method for its element: `containerProps()` targets the outer
-container, `labelProps()` targets the label (or a Select group's legend), and
-`inputProps()` targets the input, textarea, or select. Fieldset's `labelProps()`
-targets its legend. For example:
+Properties with same name and value are presumed to be boolean properties (`required`), and will placed at the end of the element.
```php
-use Eightfold\HtmlBuilder\Forms\TextInput;
+use Eightfold\HtmlBuilder\EmptyElement;
-TextInput::create('Email', 'email')
- ->containerProps('class field')
- ->labelProps('class field-label')
- ->inputProps('class form-control');
+EmptyElement::input('required required');
```
-Do not use `inputProps()` or `labelProps()` to replace generated properties
-such as `type`, `id`, `name`, `value`, or the label's `for` association. For
-example, 4.4.0 allowed `TextInput::create('Email', 'email')->inputProps('type email')`
-to change its generated type; in 5.0, use `EmailInput` instead. Use constructor
-arguments, `id()`, or the component/mode that matches the control you need.
-Setting a custom ID changes the DOM ID and its generated label and warning
-associations, but does not change the submitted field name:
-
-```php
-TextInput::create('Email', 'account_email')->id('email-field');
-// The input has id="email-field" and name="account_email".
+Output:
+```html
+
```
-### Choose Select modes and names explicitly
-
-The default remains a single dropdown. `multiple()`, `radio()`, and
-`checkbox()` explicitly choose the other modes. The initial selection is
-single-valued for dropdowns and radio groups (only the first supplied value is
-considered), and every matching value is selected for multiple dropdowns and
-checkbox groups.
-
-Dropdowns, including multiple dropdowns, and radio groups submit the supplied
-name unchanged. Include `[]` yourself when a multiple dropdown should submit a
-PHP array. Checkbox groups continue to append `[]` unless the supplied name
-already ends with it. `checked` and `multiple` passed through `inputProps()` are
-ignored; use the selection argument and mode methods instead. See
-[Select attributes and selection](#select-attributes-and-selection) for native
-attribute targets and examples.
-
-Radio and checkbox member IDs now use `-option-`
-instead of `-`. For example, option `a` in a group named
-`categories` changes from `categories-a` to `categories-option-0`. Generated
-labels follow the new IDs automatically; update external labels, CSS selectors,
-and JavaScript that reference the old IDs. `id()` changes the group's member-ID
-prefix, not its submitted name.
-
-### Update custom PropertyInterface implementations
-
-`PropertyInterface` now extends PHP's `Stringable`. Any custom implementation
-must add `public function __toString(): string` alongside `prop()` and
-`props()`.
-
-### Additive APIs and deprecated, still-working calls
-
-`containerProps()` is available for form controls, and `id()` is available on
-TextInput, EmailInput, Password, TextArea, and Select. Select also adds
-`multiple()` and `inputProps()`. These APIs are additive.
-
-`wrapperProps()` remains callable as a forwarding alias throughout 5.x. It
-continues to set the same container properties, but is deprecated in favor of
-`containerProps()` and emits `E_USER_DEPRECATED`. The native `#[Deprecated]`
-attribute is supported by PHP 8.4 and later; removal is deferred until at
-least 6.0. See [Migrating from wrapperProps](#migrating-from-wrapperprops).
-
-Escaping remains caller-owned; this upgrade does not introduce automatic
-escaping. `TextArea` is a new control, and its caller-supplied, already-escaped
-value is rendered as text content. Its leading-newline and newline-normalizing
-behavior is described in [Textarea values](#textarea-values).
-
-## Form control guidance
-
-`TextInput`, `EmailInput`, `Password`, `TextArea`, and all `Select` modes preserve help IDs
-supplied through `inputProps('aria-describedby help-id')` when adding a warning.
-With a warning, the control sets `aria-invalid="true"` and appends
-`-warning` to `aria-describedby`, removing duplicate references while
-preserving their order. Use `content()` to supply the referenced help elements.
-Overriding the control's `id()` also changes its generated warning ID.
-
-`tests/ControlGuidanceTest.php` checks these relationships across control types.
-Assertion failures identify the relationship, control type and ID, and rendered
-HTML, alongside PHPUnit's named case and expected/actual diff.
-
-### Text and email input values
-
-`TextInput` and `EmailInput` use the constructor's initial value, including an
-empty string. Caller-supplied `value` properties in `inputProps()` are ignored;
-an empty constructor value omits the attribute rather than retaining stale
-caller content. Unrelated native input properties are preserved. Values remain
-caller-sanitized: the components do not escape or encode them.
-
-Text-entry controls and dropdown selects keep each label's generated `for`
-association in sync with the control's `id()`, overriding conflicting
-`labelProps('for other')` properties while preserving unrelated label properties.
-For radio and checkbox groups, `labelProps()` targets the legend instead.
-
-### Textarea values
-
-`TextArea::create($label, $name, $value = '', $warningMessage = '')` accepts
-**caller-sanitized content** for `$value`, consistent with the other controls.
-It renders the supplied value without escaping, decoding, or double encoding.
-The implementer must escape untrusted text before passing it to the component;
-an unescaped `` can close the control and introduce markup.
-Escaping flags, character encoding, and invalid-character handling remain the
-implementer's choice.
+Properties me be added individually to facilitate dynamic construction.
```php
-use Eightfold\HtmlBuilder\Forms\TextArea;
-
-$value = "\nFirst line & second line\n";
-$escapedValue = htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
-
-TextArea::create('Notes', 'notes', $escapedValue, 'Add more detail.')
- ->inputProps('rows 4', 'required required')
- ->labelProps('class notes-label');
+use Eightfold\HtmlBuild\Element;
+
+$callout = Element::div('Information in the callout.')
+ ->props('class callout', 'aria-role alert');
+
+if ($isWarning) {
+ $callout = $callout->prop('is warning');
+}
```
-Values are element text, not a `value` attribute. Spaces, tabs, and leading,
-internal, and trailing LF newlines are preserved: rendering adds one extra
-leading newline that HTML consumes. Native HTML parsing normalizes CR and CRLF
-line endings to LF; browsers apply their native textarea submission rules.
-`inputProps()` targets the textarea and supports native `rows`, `required`,
-`readonly`, and `disabled` attributes. Generated `id` and `name` override
-conflicting input properties; `id()` also updates the label and warning
-associations. String and `PropertyInterface` warnings follow the shared guidance
-rules above.
-
-The escaping call above is an application example, not a component default.
-To display literal entity syntax such as `&`, escape that text too (for
-example, supply `&`). Callers remain responsible for sanitizing all
-values, labels, warning markup, additional content, and attribute values.
-
-### Select attributes and selection
-
-`Select::inputProps()` targets the `