diff --git a/app/docs/md/conventions/components.md b/app/docs/md/conventions/components.md
new file mode 100644
index 00000000..17400ce5
--- /dev/null
+++ b/app/docs/md/conventions/components.md
@@ -0,0 +1,259 @@
+---
+title: Components
+---
+
+Components are another option for reusable building blocks of your Enhance application. They are single file components wrapping your HTML, CSS and JavaScript in a portable web component. Components live in the `app/components/` folder in Enhance projects.
+
+## Naming
+
+The tag name of your component is determined by its file name. Meaning `app/components/my-card.mjs` will be authored as `` in your HTML page. Enhance components are HTML custom elements, so they [require two or more words separated by a dash](/docs/elements).
+
+```
+app/components/my-message →
+app/components/my-link →
+```
+
+When a project grows to include more components than can comfortably fit in a single folder, they can be divided into sub-directories inside `app/components/`.
+The folder name becomes part of the custom element tag name:
+
+```
+app/components/blog/comment →
+app/components/blog/comment-form →
+```
+
+
+
+## @enhance/custom-element
+Components are web components — meaning: they extend `HTMLElement` like vanilla web components and provide you all the [lifecycle methods](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#using_the_lifecycle_callbacks) you would expect (`connectedCallback`, `disconnectedCallback`, `adoptedCallback`, and `attributeChangedCallback`).
+
+When you write an Enhance component you will extend the `CustomElement` class from the `@enhance/custom-element` package. These single file components allow you to take advantage of slotting and style scoping in the [light DOM](https://en.wikipedia.org/wiki/Document_Object_Model) while avoiding [some of the issues](https://begin.com/blog/posts/2023-11-10-head-toward-the-light-dom) the [shadow DOM](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM) creates.
+
+
+
+```javascript
+import CustomElement from '@enhance/custom-element'
+
+export default class MyCard extends CustomElement {
+ connectedCallback() {
+ this.heading = this.querySelector('h5')
+ }
+
+ render({ html, state }) {
+ const { attrs={} } = state
+ const { title='default' } = attrs
+ return html`
+
+
+
+
${title}
+
+
+ `
+ }
+
+ static get observedAttributes() {
+ return [ 'title' ]
+ }
+
+ titleChanged(value) {
+ this.heading.textContent = value
+ }
+}
+
+customElements.define('my-card', MyCard)
+```
+
+
+
+You may be thinking that the `render` function looks familiar and you would be right. These `render` functions are Enhance Elements. This enables us to share rendering logic between the client side and server side so any Enhance Component will be server side renderable.
+
+When an Enhance component is server-side rendered it is "enhanced" with an attribute to indicate that the slotting algorithm and style transform have already been run. The attribute look like this:
+
+```html
+
+```
+
+The client-side code will look for this attribute and only run if your component hasn’t already been "enhanced". Thus avoiding an unnecessary render pass.
+
+If you have an existing Enhance Element you can always import it into your Component and use it as your `render` function.
+
+
+
+**[Learn about the HTML render function](/docs/elements/html)**
+
+
+
+## UI Updates
+Updates to Enhance Components are triggered by attribute changes. Any change to an attribute listed in the `observedAttributes` will trigger a `Changed` method. For example if you are observing the `title` attribute of our `my-card` component any time that attribute value is updated the `titleChanged` method will be executed. This enables you to write surgical DOM updates which will always be the most performant way to update your page.
+
+## DOM Diffing
+Other frameworks supply a DOM diffing solution, and Enhance Components are no different. However, we believe DOM diffing should be enabled on an opt-in basis. To enable DOM diffing in our `my-card` component, we will add the `MorphdomMixin` class from `@enhance/morphdom-mixin`.
+
+
+
+```javascript
+import CustomElement from '@enhance/custom-element'
+import MorphdomMixin from '@enhance/morphdom-mixin'
+
+export default class MyCard extends MorphdomMixin(CustomElement) {
+ render({ html, state }) {
+ const { attrs={} } = state
+ const { title='default' } = attrs
+ return html`
+
+
+
+
${title}
+
+
+ `
+ }
+
+ static get observedAttributes() {
+ return [ 'title' ]
+ }
+}
+
+customElements.define('my-card', MyCard)
+```
+
+
+
+Once added the `MorphdomMixin` will handle updating the DOM whenever an `observedAttributes` is modified. The `Changed` methods are no longer necessary. Instead on an attribute change the `render` method will be re-run and the output will be compared against the current DOM. Only the modified DOM nodes will be updated.
+
+
+
+`morphdom` does string based diffing on the actual HTML element and not a virtual DOM diff so every element you want compared needs to have a string change or unique id.
+
+
+
+
+
+### Lists
+
+When working with lists of data in the DOM it is highly advisable to add a unique attribute to the list item like an `id` or `key`. This will assist `morphdom` in determining what items have changed in the list.
+
+
+
+## Reducing Boilerplate
+Many other web components provide a way of reducing the amount of boilerplate code one needs to write. Enhance provides the `@enhance/element` package which builds upon the `CustomElement` and `MorphdomMixin` classes while providing a more succinct way of writing Enhance Components.
+
+Revisiting our `my-card` component we get:
+
+
+
+```javascript
+import enhance from '@enhance/element'
+
+enhance('my-card', {
+ attrs: [ 'title' ],
+ init(element) {
+ console.log('My Card: ', element)
+ },
+ render({ html, state }) {
+ const { attrs={} } = state
+ const { title='default' } = attrs
+ return html`
+
+
+
+
${title}
+
+
+ `
+ },
+ connected() {
+ console.log('CONNECTED')
+ },
+ disconnected() {
+ console.log('DISCONNECTED')
+ }
+})
+```
+
+
diff --git a/app/docs/md/conventions/structure.md b/app/docs/md/conventions/structure.md
index edfe499e..cc99a907 100644
--- a/app/docs/md/conventions/structure.md
+++ b/app/docs/md/conventions/structure.md
@@ -10,6 +10,8 @@ app
│ └── index.mjs
├── browser ........... browser JavaScript
│ └── index.mjs
+├── components ........ single file web components
+│ └── my-card.mjs
├── elements .......... custom element pure functions
│ └── my-header.mjs
├── pages ............. file-based routing
@@ -47,6 +49,17 @@ Elements must be [named](https://html.spec.whatwg.org/multipage/custom-elements.
+## Components
+The components folder is where you keep your single file web components. These components are rendered server-side, but also include client-side code for additional interactivity. This allows you to add progressive enhancements to your component in one file.
+
+Components follow the same rule as Elements and must be [named](https://html.spec.whatwg.org/multipage/custom-elements.html#prod-potentialcustomelementname) with one or more words separated by a dash `my-card.mjs` which corresponds to the tag name you author in your HTML pages — for example ``.
+
+
+
+**[Read more about components →](/docs/conventions/components)**
+
+
+
## API
The `api` folder is preconfigured to expose data to your file-based routes. For example, the file `app/api/index.mjs` will automatically pass state to `app/pages/index.mjs` as well as expose an endpoint for standard REST verbs like `get` and `post`.
diff --git a/app/docs/nav-data.mjs b/app/docs/nav-data.mjs
index b383561a..f1f4adf1 100644
--- a/app/docs/nav-data.mjs
+++ b/app/docs/nav-data.mjs
@@ -29,6 +29,7 @@ export const data = [
{ slug: 'css', label: 'CSS' },
'pages',
'elements',
+ 'components',
{ slug: 'api', label: 'APIs' },
'browser',
'public',
diff --git a/scripts/dictionary.txt b/scripts/dictionary.txt
index 47e91ab3..526fdf88 100644
--- a/scripts/dictionary.txt
+++ b/scripts/dictionary.txt
@@ -90,3 +90,5 @@ Neovim
nvim-treesitter
Treesitter's
parsers
+renderable
+performant