Repository navigation
Example Multi Channel Accordion
The accordion component (livingdocs/100.Misc/020.accordion/) shows how to give editors one structure in the editor that ends up as completely different markup in each output channel:
| Channel | Result |
|---|---|
| Editor | Accordion look: header bar with heading, icon and content area below |
| Web app | Interactive, accessible accordion built by Vue (button, aria-expanded, keyboard and focus handling, deep links) |
| PDF, Word, XHTML | No accordion at all: the heading and the content follow each other as if the editor had placed them without a wrapper. The content is always visible. |
The tool for this is the data-remove-from-<target> attribute on almost every element of the template (see Component Library#output-specific-markup).
flowchart LR
E["Editor template<br/>root + 5 wrapper divs + icon<br/>+ template-header + template-content"]
E -->|web export| W["srl-ld-accordion<br/>+ template-header + template-content"]
W -->|"prepareHtmlContent + accordion.vue"| A["Vue accordion<br/>h2 > button, hidden content"]
E -->|pdf / word / xhtml export| P["h2 + content<br/>(no wrappers)"]
| File | Purpose |
|---|---|
accordion.html |
Editor template with the per-channel attributes |
ld-conf.json |
Two containers: header (only title-h2 … title-h6, default title-h2) and content (default paragraph); property pdf-spacer
|
accordion.vue |
Web app rendering of <srl-ld-accordion>
|
scss/editor.scss |
Makes the custom elements template-header and template-content behave like blocks in the editor |
The app uses the SRL runtime components SrlCategoryAccordion, SrlCategoryAccordionToggle and SrlCategoryAccordionContent (.srl/components/Srl/Category/). The visual styles of .srl-category-accordion… live in the project's global SCSS (in the library's dev project: dev/src/assets/scss/components/accordion.scss).
<srl-ld-accordion class="srl-category-accordion srl-category-accordion--category"
data-remove-from-pdf="transient"
data-remove-from-word="transient"
data-remove-from-xhtml="transient"
>
<div class="srl-category-accordion__inner"
data-remove-from-web="transient" data-remove-from-pdf="transient"
data-remove-from-word="transient" data-remove-from-xhtml="transient">
<div class="srl-category-accordion__inner-head" …all four transient…>
<div class="srl-category-accordion__head" …all four transient…>
<div class="srl-category-accordion__head--btn" …all four transient…>
<template-header doc-container="header"
data-remove-from-pdf="transient"
data-remove-from-word="transient"
data-remove-from-xhtml="transient"
></template-header>
<span class="srl-category-accordion__icon"
data-remove-from-web="complete" data-remove-from-pdf="complete"
data-remove-from-word="complete" data-remove-from-xhtml="complete"
></span>
</div>
</div>
</div>
<div class="srl-category-accordion__content" …all four transient…>
<template-content class="srl-category-accordion__content-inner" doc-container="content"
data-remove-from-pdf="transient"
data-remove-from-word="transient"
data-remove-from-xhtml="transient"
></template-content>
</div>
</div>
</srl-ld-accordion>| Element | Editor | Web | Word | XHTML | |
|---|---|---|---|---|---|
<srl-ld-accordion> (root) |
✅ | ✅ → accordion.vue
|
transient | transient | transient |
__inner, __inner-head, __head, __head--btn, __content
|
✅ | transient | transient | transient | transient |
<template-header> |
✅ container | ✅ → slot #header
|
transient | transient | transient |
<template-content> |
✅ container | ✅ → slot #content
|
transient | transient | transient |
__icon |
✅ | complete | complete | complete | complete |
| Content of the containers (heading, paragraphs …) | ✅ | ✅ | ✅ | ✅ | ✅ |
Read the table in three layers:
-
Editor-only scaffolding. The five wrapper divs and the icon exist only so that the accordion looks like an accordion in the editor. They carry the same BEM classes as the Vue output, so the same
.srl-category-accordion…styles apply in the editor and in the app. -
The web skeleton. The web export keeps exactly what
accordion.vueneeds: the custom element and the twotemplate-<name>wrappers that become named slots. - The content. The components inside the containers are never touched. In PDF, Word and XHTML they are all that is left.
The icon is empty, so complete is safe. Every element that contains a doc-container or doc-editable must be transient, otherwise the editor content disappears with it.
An editor places an accordion and keeps the default title-h2 in the header (number 1, text Risks). In the content they add two paragraphs.
Simplified HTML that the app receives (Livingdocs attributes omitted):
<srl-ld-accordion class="srl-category-accordion srl-category-accordion--category">
<template-header>
<h2 class="srl-grid srl-title-h2 srl-margin-group-title-h2 srl-linkable">
<span class="srl-grid__inner srl-title-h2__number-text-container">
<span class="srl-title-h2__number">1</span>
<span class="srl-title-h2__text">Risks</span>
</span>
</h2>
</template-header>
<template-content class="srl-category-accordion__content-inner">
<p class="srl-paragraph">First paragraph …</p>
<p class="srl-paragraph">Second paragraph …</p>
</template-content>
</srl-ld-accordion>prepareHtmlContent() turns <template-header> into <template #header> and <template-content> into <template class="…" #content> (see Web App Rendering#from-html-to-vue). <srl-ld-accordion> resolves to accordion.vue.
-
Detects the heading. The first node of the
headerslot must beh1…h6. The heading number setslevel = n - 2(h2→ 0,h3→ 1 …). The root gets the modifiersrl-category-accordion--level-<level>, and the toggle rendersh<level + 2>again, so the document outline stays intact. -
Rebuilds the heading. Inside a
…__number-text-containerthe number span gets the classsrl-category-accordion__head-numberand the text spansrl-category-accordion__head-text. Without that container the first child of the heading becomes the text. All other classes of the original heading (srl-grid,srl-margin-group-…,srl-linkable, theh2-asstyling classes) are dropped. -
Wraps it in the runtime components.
SrlCategoryAccordionholds the state,SrlCategoryAccordionTogglerenders heading + button,SrlCategoryAccordionContentrenders the collapsible area. - Falls back. If the header slot does not start with a heading, the component renders the header and content slots one after the other, without accordion.
<div tabindex="-1" class="srl-category-accordion srl-category-accordion--category srl-category-accordion--level-0">
<div class="srl-category-accordion__inner">
<div class="srl-category-accordion__inner-head">
<h2 class="srl-title-h2 srl-category-accordion__head">
<button type="button" class="srl-category-accordion__head--btn"
aria-expanded="false" aria-controls="v-0">
<span class="srl-category-accordion__head-number">1</span>
<span class="srl-category-accordion__head-text">Risks</span>
<span class="srl-category-accordion__icon"></span>
</button>
</h2>
</div>
<div id="v-0" hidden tabindex="-1" class="srl-category-accordion__content">
<div class="srl-category-accordion__content-inner">
<p class="srl-paragraph">First paragraph …</p>
<p class="srl-paragraph">Second paragraph …</p>
</div>
</div>
</div>
</div>Behaviour of the runtime components:
- The button toggles the content (
hidden,aria-expanded).Escinside the content closes it and puts the focus back on the button. - Focusable elements inside a closed accordion get
tabindex="-1", so keyboard users do not tab into hidden content. -
Deep links: if the URL hash matches the
idof the accordion or of an element inside it, the accordion opens on load. For a target inside the accordion, the first such accordion of the page is also scrolled into view.
Everything but the content is gone:
<h2 class="srl-grid srl-title-h2 srl-margin-group-title-h2 srl-linkable">
<span class="srl-grid__inner srl-title-h2__number-text-container">
<span class="srl-title-h2__number">1</span>
<span class="srl-title-h2__text">Risks</span>
</span>
</h2>
<p class="srl-paragraph">First paragraph …</p>
<p class="srl-paragraph">Second paragraph …</p>This is the same markup as a title-h2 and two paragraphs placed directly in the article. The PDF uses the normal title and paragraph styles and prints the content in full. Because the root is removed from the XHTML, <srl-ld-accordion> needs no data-replace-tag.
The template has no data-remove-from-translate-plus, so the translation export keeps the complete structure.
-
Properties on the root do not reach PDF, Word or XHTML. Livingdocs writes property classes onto the component root, and the root is removed transiently there. Check in the PDF output whether a class like
pdf-spacerarrives before you rely on it. -
Heading styling options are lost in the app.
accordion.vuereplaces the heading and keeps only the number and text spans, soh2-asand similar classes only affect the editor and the print channels. -
Only one heading in the header.
accordion.vuereads only the first node of the header slot.allowedChildreninld-conf.jsonrestricts the container to titles; keep it that way. - Whitespace. The Vue component expects the heading as the first slot node and the number/text spans as element children. This works because the design build and Vue drop whitespace between tags (see Components#templates).
- Build the editor markup for the editor's look. Mark wrappers that only serve the editor
transientfor all channels. - In the web export keep only the custom element (
<srl-ld-…>) and thetemplate-<name>wrappers that the Vue component needs as slots. - Remove decorative elements without content (icons, dividers) with
complete. - Never use
completeon an element that contains adoc-containerordoc-editable. - Let channels without interaction fall back to the plain content: remove the root and all wrappers
transientfrom PDF, Word and XHTML. - Use the same BEM classes in the editor markup and the Vue output, so one stylesheet serves both.
- Give the Vue component a fallback for content it does not expect.