WLJS LogoWLJS Notebook

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:

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.

Any children passed to a tag will be automatically converted to WLXForm. If the passed expression does not have 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>
Try to drag these plots. It's alive!

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> ![](https://picsum.photos/200/300) <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"> 

![](https://picsum.photos/200/300)

<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>
You can use the same approach for markdown cells within other WLX components as well.

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:

%3Cdiv%20class%3D%22thz-lab-journal%22%3E%3Cdiv%20class%3D%22thz-header%22%3E%3Cdiv%20class%3D%22thz-title%22%3ELab%20Journal%3C%2Fdiv%3E%3Cdiv%20class%3D%22thz-subtitle%22%3ESample%3A%20ATR370%3C%2Fdiv%3E%3C%2Fdiv%3E%3Cdiv%20class%3D%22thz-grid%22%3E%3Cdiv%20class%3D%22thz-item%22%3E%3Cdiv%20class%3D%22thz-label%22%3EResolution%3C%2Fdiv%3E%3Cdiv%20class%3D%22thz-value%22%3E0.33%20cm%0A%3Csup%20%3E2%3C%2Fsup%3E%3C%2Fdiv%3E%3C%2Fdiv%3E%0A%3Cdiv%20class%3D%22thz-item%22%3E%3Cdiv%20class%3D%22thz-label%22%3EUser%3C%2Fdiv%3E%3Cdiv%20class%3D%22thz-value%22%3EPersephone%3C%2Fdiv%3E%3C%2Fdiv%3E%3Cdiv%20class%3D%22thz-item%22%3E%3Cdiv%20class%3D%22thz-label%22%3EDate%3C%2Fdiv%3E%3Cdiv%20class%3D%22thz-value%22%3ESat%2031%20Jan%202026%2014%3A36%3A01%3C%2Fdiv%3E%3C%2Fdiv%3E%3C%2Fdiv%3E%3Cdiv%20class%3D%22thz-footer%22%3E%3Cdiv%20class%3D%22thz-tag%22%3EOptics%3C%2Fdiv%3E%0A%3Cdiv%20class%3D%22thz-tag%22%3ETHZ%3C%2Fdiv%3E%3C%2Fdiv%3E%3Cstyle%20%3E%20.thz-lab-journal%20%7B%0A%20%20%20%20%20%20font-family%3A%20%22Segoe%20UI%22%2C%20Roboto%2C%20Helvetica%2C%20Arial%2C%20sans-serif%3B%0A%20%20%20%20%20%20color%3A%20%231f2933%3B%0A%20%20%20%20%20%20border%3A%201px%20solid%20%23d1d5db%3B%0A%20%20%20%20%20%20border-radius%3A%2010px%3B%0A%20%20%20%20%20%20padding%3A%2020px%2024px%3B%0A%20%20%20%20%20%20max-width%3A%20820px%3B%0A%20%20%20%20%20%20background%3A%20linear-gradient%28180deg%2C%20%23f9fafb%200%25%2C%20%23ffffff%20100%25%29%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20.thz-header%20%7B%0A%20%20%20%20%20%20display%3A%20flex%3B%0A%20%20%20%20%20%20justify-content%3A%20space-between%3B%0A%20%20%20%20%20%20align-items%3A%20baseline%3B%0A%20%20%20%20%20%20border-bottom%3A%202px%20solid%20%23e5e7eb%3B%0A%20%20%20%20%20%20padding-bottom%3A%2010px%3B%0A%20%20%20%20%20%20margin-bottom%3A%2018px%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20.thz-title%20%7B%0A%20%20%20%20%20%20font-size%3A%201.4rem%3B%0A%20%20%20%20%20%20font-weight%3A%20600%3B%0A%20%20%20%20%20%20letter-spacing%3A%200.02em%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20.thz-subtitle%20%7B%0A%20%20%20%20%20%20font-size%3A%200.9rem%3B%0A%20%20%20%20%20%20color%3A%20%236b7280%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20.thz-grid%20%7B%0A%20%20%20%20%20%20display%3A%20grid%3B%0A%20%20%20%20%20%20grid-template-columns%3A%201fr%201fr%3B%0A%20%20%20%20%20%20gap%3A%2014px%2024px%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20.thz-item%20%7B%0A%20%20%20%20%20%20display%3A%20flex%3B%0A%20%20%20%20%20%20flex-direction%3A%20column%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20.thz-label%20%7B%0A%20%20%20%20%20%20font-size%3A%200.75rem%3B%0A%20%20%20%20%20%20text-transform%3A%20uppercase%3B%0A%20%20%20%20%20%20letter-spacing%3A%200.08em%3B%0A%20%20%20%20%20%20color%3A%20%236b7280%3B%0A%20%20%20%20%20%20margin-bottom%3A%202px%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20.thz-value%20%7B%0A%20%20%20%20%20%20font-size%3A%201rem%3B%0A%20%20%20%20%20%20font-weight%3A%20500%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20.thz-footer%20%7B%0A%20%20%20%20%20%20margin-top%3A%2018px%3B%0A%20%20%20%20%20%20padding-top%3A%2012px%3B%0A%20%20%20%20%20%20border-top%3A%201px%20dashed%20%23e5e7eb%3B%0A%20%20%20%20%20%20display%3A%20flex%3B%0A%20%20%20%20%20%20gap%3A%2024px%3B%0A%20%20%20%20%20%20flex-wrap%3A%20wrap%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20.thz-tag%20%7B%0A%20%20%20%20%20%20background%3A%20%23eef2ff%3B%0A%20%20%20%20%20%20color%3A%20%233730a3%3B%0A%20%20%20%20%20%20padding%3A%206px%2012px%3B%0A%20%20%20%20%20%20border-radius%3A%20999px%3B%0A%20%20%20%20%20%20font-size%3A%200.85rem%3B%0A%20%20%20%20%20%20font-weight%3A%20500%3B%0A%20%20%20%20%7D%3C%2Fstyle%3E%3C%2Fdiv%3E

As we mentioned, you can still use Card as a standalone component:

.md <Card> <CardLabel>Parameter</CardLabel> <CardValue>11.2</CardValue> </Card>
Check out Advanced slides for ready-to-go templates that you can drop in to your notebooks.

On this page