faux-slots
v0.2.0
Published
Simulate <slot> behavior in light DOM for Preact, Lit, and vanilla web components
Maintainers
Readme
faux-slots
Why should you have to choose between the power of Shadow DOM slotting and the convenience of light DOM rendering and global styling? Well, you shouldn't.
A tiny utility to simulate <slot> behavior in light DOM. Works with Preact (via custom elements), Lit, and vanilla web components. Built for composability, styleability, and performance.
Inspired by Shadow DOM slotting, but built for real-world styling and a11y flexibility.
Get the best of both worlds:
- Native-feeling slots with named targets and default content
- Full control over DOM layout
- Light DOM rendering for predictable cascading styling
- Greater accessibility support due to nothing being hidden behind the shadow DOM
✨ Features
- Native-like
slotattribute API (default:slot="name") - Full control over slot layout and fallback content
- Works with or without Shadow DOM
- Preact, Lit, and vanilla web component support
- SSR-safe, efficient DOM placement, and
slotchangeevents
📦 Installation
npm install faux-slotsRequires preact and/or lit as peer dependencies depending on which integrations you use.
🚀 Usage
Preact custom element
import { registerFauxSlottedElement, JSXSlot } from 'faux-slots'
function MyComponent() {
return (
<div>
<header>
<JSXSlot name="header" />
</header>
<main>
<JSXSlot />
</main>
<footer>
<JSXSlot name="footer" />
</footer>
</div>
)
}
registerFauxSlottedElement(MyComponent, 'my-element')Lit element (directive)
import { html } from 'lit'
import { FauxSlotLitElement, fauxSlot } from 'faux-slots'
class MyElement extends FauxSlotLitElement {
render() {
return html`
<header ${fauxSlot('header')}></header>
<main ${fauxSlot('default')}></main>
<footer ${fauxSlot('footer')}></footer>
`
}
}
customElements.define('my-element', MyElement)Or with initFauxSlotsLit if you can't extend FauxSlotLitElement:
import { LitElement, html } from 'lit'
import { initFauxSlotsLit } from 'faux-slots'
class MyElement extends LitElement {
firstUpdated() {
initFauxSlotsLit(this)
}
render() {
return html`
<header faux-slot="header"></header>
<main faux-slot="default"></main>
`
}
}
customElements.define('my-element', MyElement)Vanilla custom element
import { attachAllSlots } from 'faux-slots'
class MyElement extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<header faux-slot="header"></header>
<main faux-slot="default"></main>
`
attachAllSlots(this)
}
}
customElements.define('my-element', MyElement)Using the element in HTML
<my-element>
<h1 slot="header">Hello!</h1>
<p>This is the default content.</p>
<span slot="footer">Footer here</span>
</my-element>🧪 Local Example
pnpm run example:preactVisit http://localhost:5173.
📚 API
Preact
registerFauxSlottedElement(Component, tag, observedAttrs?, options?)
Registers a Preact functional component as a custom element with faux slot support.
Component: Preact functional componenttag: Custom element tag name (must contain a-)observedAttrs: Attributes to observe and reflect as propsoptions:slotAttr: Attribute used to identify slotted children (default:"slot")shadow: Use Shadow DOM (default:false)
registerFauxSlottedElement(MyComponent, 'my-el', ['title'], { shadow: false })useFauxSlotTarget(name?)
Hook that returns a ref callback. Attach to a container element to receive slotted nodes for the given slot name.
const slotRef = useFauxSlotTarget('header')
return <div ref={slotRef} />useFauxStage(name?)
Like useFauxSlotTarget, but also returns refs to the container and the host element. Returns [ref, stageRef, hostRef].
const [ref, stageRef, hostRef] = useFauxStage('header')
return <div ref={ref} />JSXSlot
Declarative slot container for use inside Preact components. Renders slotted content into the container, with optional fallback.
<JSXSlot name="footer" fallback={<em>No footer provided</em>} />Props:
name: Slot name (default:"default")fallback: Content to show when the slot is emptyas: HTML tag to use as container (default:"div")- Any additional HTML attributes are forwarded to the container element
Lit
FauxSlotLitElement
Base class for LitElement. Calls collectSlots before the first render so REGISTRY is populated when directives run. Extend this instead of LitElement when using the fauxSlot directive.
fauxSlot(name?)
Lit element directive. Attach to any element in a Lit template to receive slotted content for the given slot name.
html`<div ${fauxSlot('header')}></div>`initFauxSlotsLit(host, slotAttr?)
Imperatively wires all [faux-slot] elements inside a Lit element's shadow root. Call from firstUpdated(). Installs a MutationObserver to handle dynamically added slot targets.
host: The LitElement instanceslotAttr: Attribute used to identify source nodes (default:"slot")
Vanilla
attachAllSlots(host, slotAttr?)
Scans host for elements with [faux-slot] attributes and wires slotted content into them. Installs a MutationObserver to handle dynamically added slot targets. Call from connectedCallback().
host: The custom elementslotAttr: Attribute used to identify source nodes (default:"slot")
Core engine
hasSlot(host, name?)
Returns true if the named slot has content.
hasSlot(el, 'header') // true or falsegetSlottedNodes(host, name?)
Returns the DOM nodes assigned to the named slot.
const nodes = getSlottedNodes(el, 'default')collectSlots(host, slotAttr?)
Collects and detaches slotted children from host into the internal registry. Idempotent — safe to call multiple times.
attachSlotContent(host, container, slotName?, slotAttr?)
Places the nodes for slotName into container. Dispatches a slotchange event if the DOM changed.
dispatchSlotChange(host, slot)
Dispatches a slotchange CustomEvent on the host element.
Roadmap
- [x] Default slot fallback
- [x]
slotchangeevent dispatch - [x] Shadow DOM toggle support
- [x] Custom Element compatibility
- [x] Lit directive and base class
- [x] Vanilla web component support
- [ ] Prop forwarding in JSXSlot
Author
Built with care by Caleb Pierce.
