Component-based markup
All mentioned techniques can be used in Markdown, Slide cells as well as with normal Wolfram expressions when combined with the HTMLView wrapper. Have a look at these guides as well:
WLX cells
A basic guide on how to write WLX
Styling with HTML
An introductory guide on how to shape your data using all the tools of the modern web that you can imagine and integrate it with normal Wolfram Language input/output
Advanced Slides
An advanced guide on how to build live presentations with dynamic content
What we call a component is not a special entity of WLJS, but rather an approach of using Wolfram expressions in WLX to build reusable blocks for markup. Think of it as your special custom set of HTML tags that decorate the inner content.
Reusable local component
Here is how to define and work with components within a single notebook
Headers and footers
Creating presentations is a repetitive process. It is quite common to have some elements shared between different slides. For example we want a basic header to which we can provide textual data. Let's create WLX to define it:
.wlx
Heading[OptionsPattern[]] := With[{Title = OptionValue["Title"]},
<div>
<h1><Title/></h1>
Some repetitive text you need
</div>
];
Options[Heading] = {"Title" -> ""};
Now we mark it initialization cell, so that we do not need to evaluate it manually next time. Then let's create a slide with it:
.slide
<Heading Title={"Your title"}/>
<br/><br/>
The actual content
Maybe some equations $m \mathbf{a} = \mathbf{F}$
<dummy ><div ><h1 >Your title</h1> Some repetitive text you need</div><br /><br />
The actual content
Maybe some equations $m \mathbf{a} = \mathbf{F}$</dummy> However, if we want something more complex, we can avoid passing data as options (or attributes) and render children instead.
WLXForm defined, the result is forcibly converted to a string. See later on how to alter it.Let's also add a custom icon:
.wlx
MakeTitle[Children__] := MakeTitle[ToStringRiffle[{Children}]]
MakeTitle[Child_String] := With[{
Icon = ParametricPlot[{ReIm@Exp[(I t - 0.1 t)],ReIm@Exp[(I t - 0.3 t)]}, {t,0,8Pi}, PlotStyle->{AbsoluteThickness[4], AbsoluteThickness[4]}, ImageSize->{60,60}, PlotRange->0.9{{-1,1},{-1,1}}]
},
<div class="relative flex w-full text-left flex-row gap-x-4" style="align-items: center; margin-bottom:1.5rem;">
<div style="bottom:0; z-index:1; position: absolute; background: linear-gradient(to left, orangered, lightblue); width: 100%; height: 0.7rem;"></div>
<div style="display:inline-block"><Icon/></div>
<h2><Child/></h2>
</div>
]
Footer = With[{},
<div class="w-full ml-auto mr-auto absolute text-sm" style="top: 690px">
Some repetitive footer: <i>Short title</i>, Joe Forest
</div>
];
This allows us to pass multiple child elements to our title generator. By the first definition of MakeTitle, all children will be merged into a single one. Here is an example with slides:
.slide
<!-- .slide: class="slide-standard" -->
<MakeTitle>Here is <b>our title</b></MakeTitle>
Content goes...
Content goes...
Content goes...
<Footer/>
<dummy ><!-- .slide: class="slide-standard" -->
<div class="relative flex w-full text-left flex-row gap-x-4" style="align-items: center; margin-bottom:1.5rem;"><div style="bottom:0; z-index:1; position: absolute; background: linear-gradient(to left, orangered, lightblue); width: 100%; height: 0.7rem;"></div><div style="display:inline-block">FrontEndExecutable[e67a87dd-8fcf-4627-a0d2-52585d182230]</div><h2 >Here is
<b >our title</b></h2></div>
Content goes...
Content goes...
Content goes...
<div class="w-full ml-auto mr-auto absolute text-sm" style="top: 690px"> Some repetitive footer: <i >Short title</i>, Joe Forest</div></dummy> Layout
There are built-in Row and Column wrappers from the Wolfram Standard Library that you can use on your slides. There are two ways:
Way 1: Define beforehand
square[{{imin_, imax_}, {jmin_, jmax_}}] :=
Table[
UnitStep[i - imin, imax - i] UnitStep[j - jmin, jmax - j],
{i, 0, 20}, {j, 0, 20}
]
OurGroupedPlot = Row[{
ListPlot3D[square[{{2, 5}, {3, 7}}], Mesh -> None],
ListPlot3D[Abs@Fourier@square[{{2, 5}, {3, 7}}], Mesh -> None, ColorFunction -> "Rainbow"]
}];and place on a slide or Markdown cell:
.slide
# Example
<OurPlot/>
Way 2: Assign figures separately You can also bind each to a separate variable and then assemble them into a group on a slide:
{Figure1, Figure2} = {
ListPlot3D[square[{{2, 5}, {3, 7}}], Mesh -> None],
ListPlot3D[Abs@Fourier@square[{{2, 5}, {3, 7}}], Mesh -> None, ColorFunction -> "Rainbow"]
};.slide
# Example
<Row>
<Figure1 />
<Figure2 />
</Row>
<dummy >
# Example
<div class="flex flex-row">FrontEndExecutable[4de05fe7-a44b-4f51-a71a-3284cbd182b0]
FrontEndExecutable[70e41077-f1ce-4422-a7ac-0f1f4a630668]</div></dummy> Here is another example, where we more heavily combine Markdown, WLX and HTML:
.slide
<!-- .slide: class="slide-standard" -->
## Example Slide Title
<ul>
<li>This slide serves as an example of how to format content.</li>
<li>Use this format for presenting various topics.</li>
</ul>
<br/>
<Row>

<div style="width:400px; margin-top:2rem">
- Example Content
$$
C = \frac{m}{V}
$$
where:
- $C$ is the concentration of coffee in grams per liter,
- $m$ is the mass of coffee in grams,
- $V$ is the volume of water in liters.
</div>
</Row>
<!-- .slide: class="slide-standard" -->
## Example Slide Title
<ul>
<li>This slide serves as an example of how to format content.</li>
<li>Use this format for presenting various topics.</li>
</ul>
<br/>
<div class="flex flex-row">

<div style="width:400px; margin-top:2rem">
- Example Content
$$
C = \frac{m}{V}
$$
where:
- $C$ is the concentration of coffee in grams per liter,
- $m$ is the mass of coffee in grams,
- $V$ is the volume of water in liters.
</div>
</div>
Custom container
Here’s a simple component implementation using WLX. Use the full power of web-technologies:
.wlx
Columns[data__, OptionsPattern[]] := With[{
Style = OptionValue["Style"]
},
With[{DataList = Table[
<div>
<Item />
</div>,
{Item, List[data]}
]},
<div class="flex flex-row justify-between" style="{Style}">
<DataList />
</div>
]
];
Options[Columns] = {"Style" -> ""};
Here we can not only provide children elements as arguments, but attributes as options as well. For example:
.slide
# Hey There!
<Columns>
<p>Column 1</p>
<p>Column 2</p>
</Columns>
<dummy >
# Hey There!
<div style="" class="flex flex-row justify-between"><div ><p >Column 1</p></div>
<div ><p >Column 2</p></div></div></dummy> You can safely use Markdown if this component is used on Markdown or slide cells by wrapping content in <p> tags and adding blank lines:
<Columns>
<p>
# Heading 1
</p>
<p>
# Heading 2
</p>
</Columns>Using "Style" option we can customize the look of columns:
<Columns Style={"
border-radius: 4px;
background: rgb(49 87 170);
padding: 1rem;
"}>
<p style="color: white">Heading 1</p>
<p style="color: white">Heading 2</p>
</Columns><dummy >
# Hey There!
<div style="
border-radius: 4px;
background: rgb(49 87 170);
padding: 1rem;
" class="flex flex-row justify-between"><div ><p style="color: white">Heading 1</p></div>
<div ><p style="color: white">Heading 2</p></div></div></dummy> Adding Javascript
JavaScript code can be embedded normally with <script> tags—no special magic is required. Here is an example with a helper component to display, let's say, the number of publications:
.wlx
Stat[Text_, OptionsPattern[]] := With[{
Count = OptionValue["Count"],
UId = CreateUUID[]
},
<div class="text-center text-gray-600 m-4 p-4 rounded bg-gray-100 flex flex-col">
<span style="display:hidden" id="{UId}"><Count /></span>
<span class="text-md"><Text /></span>
<script type="module">
const c = document.getElementById('<UId/>');
const final = Number(c.innerText);
c.innerText = 0; c.style.display = "";
let cnt = 0;
const int = setInterval(()=>{
c.innerText=cnt++;
if (cnt > final) clearInterval(int);
}, 100);
</script>
</div>
]
Options[Stat] = {"Count" -> 1};
.slide
# Basic counter
<Stat Count={11}>Number of publications</Stat>
Note that our counter will run immediately—that is, if you have many slides, you will always see a static number. You need to subscribe to SlideEventListener to check if the slide is visible. See this guide:
Reusable remote components
You can reuse components across different notebooks as modules. WLJS provides an interface to define and evaluate a normal notebook as an isolated module:
Pattern matching
Here we use a different approach to constructing components—instead of converting everything to WLXForm strings, we keep a symbolic representation of each child element as a tree of expressions. This is similar to both XML and pattern matching in Wolfram Language. As stated in w3schools:
HTML was designed to display data - with focus on how data looks XML was designed to carry data - with focus on what data is
In this chapter we use WLX as the second one. This allows to declare data expressions such as:
.slide
<Card>
<CardTitle>Pluto excitation spectra</CardTitle>
<CardImage Link={"link/to/an/image"}/>
<CardCredits>Song Goo et al. <b>Nature Programming</b> 33 2027</CardCredits>
</Card>
The Card component is responsible for displaying this data.
Slides and markdown cells inherit features from WLX cells. By default, the WLX parser will pass all arguments as strings using WLXForm or ToString if the symbol does not have this output form defined. We need to preserve our data-symbols to prevent them from being converted to strings:
CardTitle /: ToString[c_CardTitle, WLXForm] := c
CardCredits /: ToString[c_CardCredits, WLXForm] := c
CardImage /: ToString[c_CardImage, WLXForm] := c
Card[__] := "Unknown pattern";
SetAttributes[Card, Orderless];Now CardTitle, CardCredits, CardImage in the context of WLX will not be transformed before reaching the parent element. This allows us to use the full power of native pattern matching with them on Card symbol:
.wlx
Card[CardTitle[Title_], CardCredits[credits__]] := With[{Text = ToStringRiffle[{credits}]},
<div>
<h5><Title/></h5>
<span style="font-size:smaller"><Text/></span>
</div>
]
Card[CardTitle[Title_], CardCredits[credits__], CardImage[Rule["Link", url_]]] := With[{Text = ToStringRiffle[{credits}]},
<div>
<img src="{url}" width="400" style="scale:1.2"/>
<br/>
<h5><Title/></h5>
<span style="font-size:smaller"><Text/></span>
</div>
]
Here is an example of a slide created using this approach with 2 cards in Row container:
<dummy >
## A row or cards
<div class="flex flex-row"><div class="flex flex-col text-center items-center"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/c/cb/Reverend_Robert_Walker_%281755_-_1808%29_Skating_on_Duddingston_Loch.jpg/256px-Reverend_Robert_Walker_%281755_-_1808%29_Skating_on_Duddingston_Loch.jpg?20190701200849" width="300"/><br /><h4 style="max-width:15rem; text-wrap: auto;">Skating on Duddingston Loch</h4><div style="max-width:15rem; text-wrap: auto;" style="font-size:smaller">Reverend Robert Walker (1755 - 1808), via Wikimedia</div></div>
<div class="flex flex-col text-center items-center"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/6/67/Robert_Howlett_%28Isambard_Kingdom_Brunel_Standing_Before_the_Launching_Chains_of_the_Great_Eastern%29%2C_The_Metropolitan_Museum_of_Art_-_restoration1.jpg/256px-Robert_Howlett_%28Isambard_Kingdom_Brunel_Standing_Before_the_Launching_Chains_of_the_Great_Eastern%29%2C_The_Metropolitan_Museum_of_Art_-_restoration1.jpg?20220327171342" width="300"/><br /><h4 style="max-width:15rem; text-wrap: auto;">...</h4><div style="max-width:15rem; text-wrap: auto;" style="font-size:smaller">Robert Howlett, via Wikimedia Commons</div></div></div></dummy> More examples
You can create a similar helper component for textual cells as well. WLJS Notebooks are designed for creating lab journals and interactive reports. You can also create a template notebook with your custom components in a hidden initialization cell.
Imagine a Lab Journal. Then we need nice-looking header with a title, some parameters and possible tags. Let's start with:
.wlx
ClearAll[LabJournal];
LabJournal[Content__, opts: OptionsPattern[]] := LabJournal[ToStringRiffle[{Content}], opts];
LabJournal[Content_, OptionsPattern[]] := With[{
Title = OptionValue["Title"],
Subtitle = OptionValue["Subtitle"],
tags = OptionValue["Tags"]
},
{
Footer = If[Length[tags] == 0, "",
With[{Tags = Table[
<div class="thz-tag"><Tag/></div>
, {Tag, tags}]},
<div class="thz-footer">
<Tags/>
</div>
]
]
},
<div class="thz-lab-journal">
<div class="thz-header">
<div class="thz-title"><Title/></div>
<div class="thz-subtitle"><Subtitle/></div>
</div>
<div class="thz-grid">
<Content/>
<div class="thz-item">
<div class="thz-label">Date</div>
<div class="thz-value"><Now/></div>
</div>
</div>
<Footer/>
</div>
];
Options[LabJournal] = {"Title"->"Lab Journal", "Subtitle"->"", "Tags"->{}};
Here is a list of corresponding styles:
Place it to the end of WLX cell or create a separate one (HTML or WLX type). LabJournal itself does not use any special pattern matching and is meant to just render any children in a nice grid-like structure:
.md
<LabJournal
Title={"Lab Journal"}
Subtitle={"Sample: ATR370"}
Tags={{"Optics", "THZ"}}
>
Here there will be grid of cards
</LabJournal>
For rendering key-value structures, we define a separate symbol Card with data-holding symbols CardLabel and CardValue. Since it is not bound to LabJournal, we can reuse it as a separate component elsewhere:
.wlx
ClearAll[Card];
Card[_] := "Undefined pattern";
Card[__] := "Undefined patterns";
Card[CardLabel[LabelContent_], CardValue[ValueContent_]] := With[{},
<div class="thz-item">
<div class="thz-label"><LabelContent/></div>
<div class="thz-value"><ValueContent/></div>
</div>
];
Card[CardLabel[LabelContent_], CardValue[ValueContent__]] := Card[CardLabel[LabelContent], CardValue[ToStringRiffle[{ValueContent}]]]
Card[CardLabel[LabelContent__], CardValue[ValueContent__]] := Card[CardLabel[ToStringRiffle[{LabelContent}]], CardValue[ToStringRiffle[{ValueContent}]]]
Card[CardLabel[LabelContent__], CardValue[ValueContent_]] := Card[CardLabel[ToStringRiffle[{LabelContent}]], CardValue[ValueContent]]
CardLabel /: ToString[c_CardLabel, WLXForm] := c;
CardValue /: ToString[c_CardValue, WLXForm] := c;
SetAttributes[Card, Orderless];
We've also handled different patterns—for instance, when CardValue has multiple arguments passed to it while CardLabel does not. This can be done elegantly using Wolfram Language pattern matching. Now, let's combine both components to create a nice header for our report using Markdown cells:
.md
<!-- Hide this cell using Alt-2 or Cmd-2 -->
<LabJournal
Title={"Lab Journal"}
Subtitle={"Sample: ATR370"}
Tags={{"Optics", "THZ"}}
>
<Card>
<CardLabel>Resolution</CardLabel>
<CardValue>0.33 cm<sup>2</sup></CardValue>
</Card>
<Card>
<CardLabel>User</CardLabel>
<CardValue>Persephone</CardValue>
</Card>
</LabJournal>
Here is how the final result looks:
As we mentioned, you can still use Card as a standalone component:
.md
<Card>
<CardLabel>Parameter</CardLabel>
<CardValue>11.2</CardValue>
</Card>