diff --git a/docs/1-essentials/02-views.md b/docs/1-essentials/02-views.md
index 3db0f453c..e56dec8b1 100644
--- a/docs/1-essentials/02-views.md
+++ b/docs/1-essentials/02-views.md
@@ -341,21 +341,128 @@ The idiomatic way of using attributes is to always use `{txt}kebab-case`.
### Fallthrough attributes
-When `{html}class` and `{html}style` attributes are used on a view component, they will automatically be added to the root node, or merged with the existing attribute if it already exists.
+When `{html}class` and `{html}style` attributes, or `{html}id` is provided on a view component, Tempest will attempt to automatically apply these to the root node within the view component.
+:::info
+In previous releases (3.8.0 and prior), Tempest would attempt to *merge* these values, however there was no way to prevent this, or customise the behaviour. There was also a bug in applying the attributes, which meant that in many cases it didn't apply at all, resulting in inconsistent behaviour. This has been resolved, but has a new default behaviour, as explained below.
+:::
+
+Assume you have a `button`, like so, with a default set of classes present:
```html x-button.view.php
+```
+Now, in your page, you may utilise the element:
+```html index.view.php
+
+```
+As these attributes automatically apply, your button will be converted to this:
+```html
+
+```
+
+#### Disabling automatic fallthrough
+
+Tempest will attempt to apply `{html}class`, `{html}style`, and `{html}id` automatically, when they are passed to a view component. For example:
+```html index.view.php
+
+```
+With the above, Tempest will attempt to apply `{html}style`, and `{html}id` automatically. As `{html}class` isn't configured, it isn't applied.
+
+In the view component itself, you can configure `{html}class`, `{html}style`, and `{html}id` to anything you want, and Tempest will not overwrite them. You can of course, also then use these classes however you want to use them:
+```html x-button.view.php
+
+```
+When you use this version of ``:
+- `{html}id` will now default to `mybtn_(sequence generated by uniqid)`,
+- `{html}style` will not appear automatically, as it was not supplied,
+- `{html}class` will have a default, you can of course instead concatenate these strings, or use a CVA utility for smart class merging, or anything you want.
+
+For example, pass one or more classes:
+```html
+
+```
+And you'll get
+```html
+
+```
+
+### Controlling fallthrough attributes with the Apply attribute
+
+You can also leverage the `ApplyAttribute` to completely control the behaviour, and add further fallthrough attributes, if you wish. When `:apply` is detected on a view component, Tempest will disable all automatic fallthrough attributes, for that instance of the view component. If you are familiar with JS frontend frameworks, this is not dissimilar to a one-way `v-bind` in Vue, or a spread props operator in other languages.
+
+By default, `$attributes` is an `ImmutableArray` and so we can manipulate it with the methods available on that class.
+
+:::info
+You cannot mix `ApplyAttribute` with automatic fallthrough attributes. Opting to use the `ApplyAttribute` hands you full control of which attributes are applied, which means you then need to declare these.
+:::
+
+#### Excluding specific fallthrough attributes
+
+To exclude specific attributes from falling through, configure your `button` view component like this:
+```html x-button.view.php
+
```
+:::info
+Why array_flip? In `$attributes` the keys are the attributes, in the array in the example above, the values are the attributes, you could also pass `['class' => 0, 'width' => 1, etc]` without a flip.
+:::
+Now, when utilising it in your page:
+```html index.view.php
+
+```
+Will result in:
+```html
+
+```
-The example above defines a button component with a default set of classes. Using this component and providing another set of classes will merge them together:
+#### Including only specific fallthrough attributes
+To include only specific attributes, configure your `button` view component like this:
+```html x-button.view.php
+
+```
+:::info
+Why array_flip? In `$attributes` the keys are the attributes, in the array in the example above, the values are the attributes, you could also pass `['class' => 0, 'width' => 1, etc]` without a flip.
+:::
+Now, when utilising it in your page:
```html index.view.php
-
+
```
+Tempest will apply only the specified attributes:
+```html
+
+```
+
+#### Advanced usage of the Apply attribute
+
+As the `ApplyAttribute` simply stringifies string and boolean values from the provided array, you can build the array however you like.
-Similarly, the `id` attribute will always replace an existing `id` attribute on the root node of a view component.
+Consider this example `button`:
+```php x-button.view.php
+ $class ?? null,
+ 'href' => $href ?? '',
+ 'target' => (isset($href) && str_contains($href, 'http')) ? '_blank' : null,
+];
+?>
+
+```
+Now, when utilising it in your page:
+```html index.view.php
+
+```
+Tempest will spread the supplied attributes, and as we also used the `AsAttribute` to convert it to a `{html}a` when `$href` is populated, you will get a hyperlink:
+```html
+Tempest, the framework that gets out of your way
+```
### Dynamic attributes
diff --git a/packages/view/src/Attributes/ApplyAttribute.php b/packages/view/src/Attributes/ApplyAttribute.php
new file mode 100644
index 000000000..0b700c434
--- /dev/null
+++ b/packages/view/src/Attributes/ApplyAttribute.php
@@ -0,0 +1,93 @@
+consumeAttribute(':apply');
+
+ if ($value === null || trim($value) === '') {
+ return $element;
+ }
+
+ if ($element instanceof ViewComponentElement) {
+ $element->setApplyExpression($value);
+
+ return $element;
+ }
+
+ $element->addRawAttribute(sprintf(
+ '= \%s::renderAll(%s) ?>',
+ self::class,
+ $value,
+ ));
+
+ return $element;
+ }
+
+ /**
+ * Stringifies an ImmutableArray or plain array of attributes into an HTML attribute string.
+ *
+ * Rules:
+ * - boolean true → bare attribute name (e.g. `disabled`)
+ * - boolean false → omitted
+ * - null → omitted
+ * - empty string → omitted
+ * - int / float → name="value" (cast to string — HTML attributes are always strings)
+ * - string → name="value"
+ * - array → name="space-joined values" (via ExpressionAttribute::resolveValue)
+ *
+ * Returns 'key="val" key2="val2"' with NO leading space. GenericElement's compile()
+ * inserts one space before the raw attribute block at compile time, so adding a leading
+ * space here would produce a double space in the rendered output.
+ *
+ * Note: when this returns '' (all attributes omitted), GenericElement's compile-time
+ * space still appears, producing e.g. `