When a Module needs to hand its component a bit of scalar configuration — a
Splide config object, a set of flags, library options — it writes that config as a
JSON string into a data-* attribute. On construction the instance parses it into
options, and your JS reads it from there. No fetch, no lookup: the config rides
along on the markup.
The PHP side
json_encode the config, then echo it into a data-* attribute. Here Intro
builds its carousel options and hangs them off data-splide-gallery:
$splide = json_encode([
'arrows' => true,
'pagination' => true,
'perPage' => 3,
'gap' => '2.5rem',
'focus' => 'center',
'breakpoints' => [
'1200' => ['perPage' => 2],
'640' => ['perPage' => 1],
],
]);
<div class="intro__items splide-gallery" data-splide-gallery="<?= esc_attr($splide); ?>">
Always run the encoded JSON through esc_attr() before printing it into the
attribute. The value ends up inside double quotes in the HTML, so an unescaped
quote in the payload breaks the markup.
The JS side
You never parse the attribute yourself. The framework reads it once, at
construction, and the result lands on this.options — ready before your first
lifecycle method runs. Your class does two things: declare a static get defaults()
baseline for when the attribute is absent, then read this.options wherever you
need the config. Here SplideGallery hands the merged options straight to Splide:
class SplideGallery extends Instantiable {
static selector = '.splide-gallery'
static get defaults() {
return { type: 'fade', perPage: 1, arrows: false, pagination: false }
}
constructor(element, options) {
super(element, options)
// the data-* payload, merged over defaults, is already on this.options
this.#controller = new Splide(this.element.querySelector('.carousel'), this.options)
}
}
The attribute name matches the key camel-cased: data-splide-gallery is read by
the class whose moniker resolves to splideGallery. Match the two and the payload
arrives on this.options with nothing else to wire — the framework picks the key by
moniker (falling back to the class name, or an explicit static dataKey),
JSON.parses it, and falls back to defaults when the attribute is absent.
Why an attribute and not an island. An attribute value is a single string the
browser already parsed into dataset, and parseOptions runs it before your
first lifecycle method — so config is present the moment the instance exists.
That's ideal for scalar, bounded config. The moment the payload becomes a
collection — a list of gallery items, a set of map markers — it outgrows an
attribute and belongs in a Data island.
When to use it
- Library options (Splide, Fancybox) the component passes straight through.
- Feature flags and small enums (
layout,pois,cluster). - Any bounded, scalar config you'd be comfortable reading inline in the HTML.
Reach for a Data island instead when the payload is a collection or deeply nested structure.
Related
- Data islands — the sibling bridge for collections and structured payloads.
- Fields → CSS — how editor style settings cross to SCSS instead of JS.
- The lifecycle — when
optionsis available relative toboot*/init*.