Notebooks
WLJS Notebook provides a rich API for programmatic manipulation of notebooks, cells, and windows compatible with Mathematica.
Programmatic notebook generation
You can create and manipulate notebooks programmatically using several core WL functions.
The API uses
RemoteCellObjandRemoteNotebookobjects to operate with cells and notebooks. We call them remote, since the evaluation kernel is generally isolated from the notebooks and in theory it is not necessary to run on the same physical machine, while all actual operations are performed by the host process of the app.
Creating notebooks
Use CreateDocument to create a new notebook populated with cells:
(* Create a notebook with text and a plot *)
nb = CreateDocument[{
"Here is a plot example",
Plot[x, {x, 0, 1}]
}];
(* Create with specific cell styles *)
CreateDocument[{
TextCell["My Section", "Section"],
ExpressionCell[Plot[x, {x,0,1}], "Input"]
}];
(* Create invisibly for background processing *)
nb = CreateDocument[{"Content"}, Visible -> False];Use Defer to prevent evaluation:
CreateDocument[Defer[1 + 1]];To save a created notebook to a file, use NotebookSave:
NotebookSave[nb, "path/to/notebook.wln"];Cell Expressions
Here is a list of supported expressions in CreateDocument, CellPrint, NotebookWrite and CreateWindow:
- Any expression → converted
StandardForminput cell _String→ converted to text/markdown cellExpressionCell[expr_, "Input"]ExpressionCell[expr_, "Output"]whereexprwill be converted toStandardFormTextCell[expr_]represent text/markdown cellsTextCell[expr_, "Title"]TextCell[expr_, "Section"]TextCell[expr_, "Subsection"]represent styled markdown cellsCell[expr_String, "Input"]Cell[expr_String, "Output"]represents complete input and output cellsCell[expr_String, "Input", subtype]represents complete output cell with asubtype, which sets a renderer forexprcontent:"md"or"markdown"or"Markdown"will use markdown renderer,"js"or"javascript"will treatexpras Javascript expression aka Javascript Cells,"html"or"HTML"will render text content as HTML document,"mermaid"or"Mermaid"will render text content as Mermaid diagram,- ...
The number of cell subtypes roughly corresponds to the number of available cell types in WLJS Notebook.
For example, let's create a document with HTML output cell:
CreateDocument[{
Cell["<h2>Hello World</h2>", "Input", "html"],
Cell["<h2>Hello World</h2>", "Output", "html"]
}]Importing and opening notebooks
Use NotebookOpen to import a notebook:
(* Open a notebook by path *)
nb = NotebookOpen["path/to/notebook.wln"];
(* Open invisibly for background processing *)
nb = NotebookOpen["path/to/notebook.wln", Visible -> False];or to show an imported one:
nb = NotebookOpen["path/to/notebook.wln", Visible -> False];
(* do something with a notebook *)
(* Open in a window *)
NotebookOpen[nb];Accessing evaluation context
Use EvaluationNotebook to get a reference to the notebook where your code is running:
nb = EvaluationNotebook[];EvaluationNotebook[] uses evaluation context to get the caller notebook object. It will not work correctly if called from a button click, timer or any other event:
Button[
"Click",
(* context is lost ❌ *)
Print[EvaluationNotebook[]]
] Notebook evaluation
To evaluate a notebook in the global context, use NotebookEvaluate
NotebookEvaluate["path/to/notebook.wln"]This will evaluate all cells in the provided notebook using the same Kernel.
Cells manipulation
Creating cells
Use CellPrint to insert a new cell below the currently evaluated one:
(* Print an expression as output *)
cell = CellPrint[Plot[Sin[x], {x, 0, 2Pi}]];
(* Print with specific cell type *)
CellPrint[Cell["<h1>Hello World</h1>", "Output", "HTML"]];
(* Print a text cell *)
CellPrint[TextCell["This is a section", "Section"]];To delete created cell, simply call NotebookDelete on cell:
NotebookDelete[cell];Use NotebookWrite for more control over where cells are inserted:
(* Append to a notebook *)
nb = EvaluationNotebook[];
NotebookWrite[nb, Plot[x, {x, 0, 1}]];
(* Insert after a specific cell *)
NotebookWrite[NotebookLocationSpecifier[cell, "After"], expr];
(* Replace a specific cell *)
NotebookWrite[NotebookLocationSpecifier[cell, "On"], expr];NotebookWrite returns a list of created cell objects or a single cell object.
Accessing evaluation context
Use EvaluationNotebook to get the notebook containing the currently evaluated cell:
nb = EvaluationNotebook[];Here is an example where you can append a cell by clicking a button (which is not possible with CellPrint):
nb = EvaluationNotebook[];
Button["Create", NotebookWrite[nb, Style["Hi there!", Bold]]]Use EvaluationCell to get a reference to the input cell that triggered the current evaluation:
cell = EvaluationCell[];and add a cell after:
NotebookWrite[NotebookLocationSpecifier[cell, "After"], Red];Use ResultCell to get a future reference to the output cell (even before it's created):
outputCell = ResultCell[];ResultCell can be useful for tracking cell events or deleting it under certain conditions automaticallyUse NotebookFocusedCell to get a focused / selected cell in the notebook:
focusedCell = NotebookFocusedCell[]Alternatively, you can apply NotebookRead to get selected cells of a given notebook:
focusedCell = NotebookRead[EvaluationNotebook[]]Writing to existing cells
Use NotebookWrite on cell objects to overwrite their content:
NotebookWrite[EvaluationCell[], "Hi there!"];Here is another example with live updates:
cell = NotebookWrite[EvaluationNotebook[], Cell["Timer", "Input"]];
timer = SetInterval[NotebookWrite[cell, Now], 1000];
Button["Delete cell",
TaskRemove[timer];
NotebookDelete[cell];
]Reading cells
Use NotebookRead to read cell contents:
(* Read the focused cell *)
content = NotebookRead[NotebookFocusedCell[]];List all cells
Use Cells to list all cell objects in the notebook
cells = Cells[];
Take[NotebookRead[cells], UpTo[5]]Deleting cells
Use NotebookDelete to remove cells:
cell = CellPrint[Now];
(* Later... *)
NotebookDelete[cell];Hidden input cells
You can create a pair of input-output cell with a hidden input one using CellOpen->False:
focused = NotebookFocusedCell[];
input = NotebookWrite[NotebookLocationSpecifier[focused, "After"], Cell["1+1", "Input", CellOpen->False]];
NotebookWrite[NotebookLocationSpecifier[input, "After"], Cell["2", "Output"]];Cell event handlers
You can attach event handlers to cells to react to lifecycle events:
With[{cell = ResultCell[]},
EventHandler[cell, {
"Destroy" -> (Print["Cell was removed!"]&)
}];
"Delete me"
]This can come in handy to remove listeners, stop timers, or other processes when you reevaluate the same cell.
Example: Self-destructing cell
With[{cell = EvaluationCell[]},
EventHandler[InputButton["Delete me"], Function[Null,
cell // NotebookDelete
]]
]Expression windows manipulation
Note that created expression windows and cells in fact share the same object structure: RemoteCellObj
Creating windows
Use CreateWindow to open content in a new expression window:
(* Open graphics in a new window *)
win = CreateWindow[Graphics3D[Cuboid[]]];
(* With size and title *)
CreateWindow[Plot[Sin[x], {x, 0, 2Pi}, ImageSize->{300,300}],
WindowSize -> {400, 400},
WindowTitle -> "My Plot"
];
(* HTML content *)
CreateWindow[Cell["<h2 style=\"color:red\">Hello</h2>", "Output", "HTML"]];Closing windows
win = CreateWindow[Graphics3D[Sphere[]]];
Pause[3];
NotebookClose[win];Create window from another window
CreateWindow[Button["Open", CreateWindow[Plot3D[x y, {x,-1,1}, {y,-1,1}]]]]Tracking window events
You can attach event handlers to created windows:
state = "";
TextView[state // Offload, "Label" -> "State"]
win = CreateWindow[ExpressionCell[Plot[x, {x, 0, 1}], "Output"]];
EventHandler[win, {
"Mounted" -> Function[Null, state = "Mounted"],
"Closed" -> Function[Null, state = "Closed"]
}];Application windows manipulation
Each visible window of WLJS Notebook can be retrieved from the context using CurrentWindow
appWindow = CurrentWindow[]This returns WindowObj, that keeps socket connection to it.
The following rules apply:
- Each opened application window has a unique
WindowObj - Any function calls by UI events implicitly provide
WindowObjas well, which can be accessed usingCurrentWindow[] - Evaluation context also contains current
WindowObjif the notebook is open WindowObjis required for certain actions such as script execution on the window done by FrontSubmit and others
Tracking window events
You can attach event handlers to WindowObj:
state = "Open";
EventHandler[CurrentWindow[], {
"Closed" -> Function[Null,
state = "Closed";
]
}];
TextView[state // Offload]Now try to reload a window or close/open a notebook—you will see the change in the state.
Cells as data storage
Programmatic control over the notebook structure allows you to reuse cells as storage for your data. For example, Cell representation automatically picks up a cell type if applicable. If you want to call a Python script from the session, where should you store .py expressions? Why not store them in another input cell and leave the name in the first line?
np.py
import numpy as np
new_array = np.linspace(0,10,11).astype('int');
new_arrayThen we can locate this particular cell. Let's define a helper function:
getPythonCell[name_String] := StringDrop[SelectFirst[NotebookRead[Cells[]], MatchQ[Cell[_,_,name<>".py"]]][[1]], StringLength[name]+4]and evaluate our script:
scriptText = getPythonCell["np"];
ExternalEvaluate["Python", scriptText];Notebooks as modules
Use NotebookEvaluateAsModule to import notebooks as reusable modules:
{exports} = NotebookEvaluateAsModule["myLibrary.wln"];NotebookEvaluateAsModule call breaks the evaluation order and may cause double evaluation of the caller cell. Please consider to use non-blocking NotebookEvaluateAsModuleAsyncThe following rules apply:
- Initialization cells are evaluated once per notebook object
- The last Wolfram input cell is evaluated on every call and its result is exported
- All other cells are ignored
- Context of all global symbols will be completely isolated (lexically scoped)
Example 1: Creating a component library
Library.wln:
.wlx
BigHeader[Text_String] := <h1><Text/></h1>;{BigHeader}Main.wln:
{HeaderComponent} = NotebookEvaluateAsModule["Library.wln"];.slide
<HeaderComponent>Hi there!</HeaderComponent>
This is my slideFor non-blocking imports, use NotebookEvaluateAsModuleAsync:
Then[NotebookEvaluateAsModuleAsync["Library.wln"], Function[exports,
{HeaderComponent} = exports;
]];Example 2: Creating a component library
Library.wln:
w3D[x_,y_,t_] := Total@Table[With[{kx = 1.0 \[Omega], ky = 1.0 \[Omega]},
Cos[ kx x + ky y - \[Omega] t]
], {\[Omega], 0, 3, 1.0}];
widget[] := Animate[Plot3D[w3D[x,y,t], {x,-Pi,Pi}, {y,-Pi,Pi}, MaxRecursion->0, Mesh->None, PlotRange->Full], {t,0,5}];widgetMain.wln:
animation = NotebookEvaluateAsModule["Library.wln"];(* use it *)
animation[]This approach may be more preferable for small modules than a traditional Wolfram Package:
- Easy to use, automatic context isolation
- All tests and examples are in the same notebook
- It allows to utilize non-standard cell types
Notebooks as templates
WLJS Notebook includes a template system for quickly creating new notebooks with predefined styles, content, and structure. Templates are accessible via the Command Palette or via File menu.
Template locations
| Location | Path |
|---|---|
| User templates | ~/Documents/WLJS Notebooks/UserTemplates/ |
| Built-in templates | AppExeFolder/app/wljs-packages/wljs-templates/Library/ |
How templates work
A template is simply a regular notebook. When you create a new notebook from a template:
- The notebook file is copied to your project directory
- If an
attachmentsfolder exists alongside the template, it is also copied - The notebook opens with all cells and styles intact
The filename of the template notebook becomes the title shown in the template list.
Creating a custom template
Templates typically contain:
- Style cells — HTML/CSS that override default notebook appearance
- Banner or header content (optionally) — Images or formatted text or Javascript code
- Hidden initialization cells - adds predefined symbols to the session
- Boilerplate cells — Pre-written code or documentation structure
Step 1: Create the notebook
Start with a new empty notebook and add your content.
Step 2: Add custom styles (optional)
Create an .html cell with CSS to customize the notebook appearance:
.html
<style>
:root {
--editor-key-keyword: #708;
--editor-key-string: #a11;
}
body {
background: #fafafa;
}
h1 {
color: #5e8a8b;
}
</style>Evaluate the cell to apply the styles.
See Global Styling for a complete list of available CSS classes and variables.
Step 3: Hide style cells
For a clean template experience, you should hide the style cells using cell group properties:
- Vanish — Makes the cell completely invisible and uneditable, but its output remains in the DOM (ideal for CSS/JS injection)
or
- Lock + Hide — Keeps the output visible but prevents editing
Vanished cells can be viewed and edited in Expert mode (see Settings).
Step 4: Add hidden initialization cell (optional)
If you need to provide define some symbols:
- create a new cell at the top of the notebook;
- add the definitions;
- apply Vanish to this input cell.
Step 5: Add a banner (optional)
Create an .md cell with a banner image:
.md
<img src="/attachments/banner.png" style="width: 100%; height: 200px; object-fit: cover;"/>Apply Lock and Hide properties of a cell group to keep the banner visible but uneditable.
Step 5: Add boilerplate cells
Create as many preformatted cells as you want
Step 6: Save and install
- Save your notebook
- Create a folder in
~/Documents/WLJS Notebooks/UserTemplates/with a descriptive name - Move your notebook (and
attachmentsfolder if any) into this folder - Restart the app
Your template will now appear in the Command Palette under "New from Template".
Global Styling
You can apply custom CSS styles to all notebooks by adding them via Settings → Custom CSS. These styles are injected into the document head and apply globally.
Adding global styles
The styles will be applied to all windows automatically.
Available CSS classes
WLJS Notebook exposes predefined CSS classes for styling various elements:
Document structure
| Selector | Description |
|---|---|
body | The whole document |
main | Main window container |
.ccontainer | Cells container (extends to full size of main) |
.cgroup | A single group of cells: input + outputs + tools |
.cframe | Inner group of cells: input + outputs |
.cborder | Vertical line at the right side of cell group |
.cwrapper | Input/output cell wrapper |
.cseparator | Thin space between cells |
Cell states and types
| Selector | Description |
|---|---|
.cinit | Initialization cells |
.cin | Input cells parent element |
.cout | Output cells parent element |
.ttint | Focused cells |
.cgi-ico | Initialization cell group icon (teal dot) |
#sidebar-right | Right sidebar in exported HTML |
Input cell languages
| Selector | Description |
|---|---|
.clang-generic | Unknown cell type / language |
.clang-markdown | Markdown cells |
.clang-html | HTML cells |
.clang-wlx | WLX cells |
.clang-js | JavaScript cells |
.clang-slide | Slide cells |
Wolfram Language input cells have an empty class field.
Markdown output
| Selector | Description |
|---|---|
.cout.markdown | Markdown output cells |
.markdown h1 | Heading level 1 in markdown |
.markdown h2 | Heading level 2 in markdown |
.markdown p | Paragraphs in markdown |
Styling examples
Change notebook background
.ccontainer {
background: lightblue;
}
body {
background: lightblue !important;
}
main {
background: lightblue !important;
}Style the input cell border marker
The vertical line on the left side of input cells:
.cin > :nth-child(2)::after {
border-color: #5e8a8b;
}Custom heading colors
.markdown h1 {
color: #2e7d32;
}
.markdown h2 {
color: #1565c0;
}Editor color variables
The following CSS variables control syntax highlighting and UI colors. Override them in :root:
:root {
/* Syntax highlighting */
--editor-key-meta: #404740;
--editor-key-keyword: #708;
--editor-key-atom: #219;
--editor-key-literal: #164;
--editor-key-string: #a11;
--editor-key-escape: #e40;
--editor-key-variable: #00f;
--editor-local-variable: #30a;
--editor-key-type: #085;
--editor-key-class: #167;
--editor-special-variable: #256;
--editor-key-property: #00c;
--editor-key-comment: #940;
--editor-key-invalid: #f00;
--editor-outline: #696969;
/* Typography */
font-size: medium;
font-family: system-ui, -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
'Open Sans', 'Helvetica Neue', sans-serif;
}Global styles are also copied to exported standalone HTML files.
Notebooks as Mini Apps
Let's transform an interactive notebook to a mini app. We will parameterize this 3D plot and add export to an STL file:
Create our first cell and add it to initialization group
model;
Model = Manipulate[model = RevolutionPlot3D[{Sin[t] + Sin[y t] /m,
Cos[t] + Cos[x t] /m}, {t, 0, Pi},
RegionFunction -> (Sin[5 (#4 + #5)] > 0 &), Mesh -> None,
BoundaryStyle -> Black, PlotStyle -> Thickness[.1], MaxRecursion->1, Axes->False, ImageSize->400], {{x,5, "tX"}, 1, 10, 1}, {{y, 5, "tY"},1,10,1}, {{m,5.5,"Scale"},1,10,0.5}, Appearance->None, ContinuousAction->True];Now create another one, where we define an exporting function:
export := Then[SystemDialogInputAsync["FileSave", {Null, {"STL" -> {"*.stl"}}}],
Function[path,
If[!StringQ[path], Return[]];
Export[path, model];
]
]; Here we used SystemDialogInputAsync instead of SystemDialogInput, since the latter is blocking and cannot be used in calls generated by external asynchronous events such as button clicks.
Notebooks exported as mini apps use the very last cell's output as the main window. To take advantage of better customization, we use a WLX cell as the output instead of a plain Wolfram language cell:
.wlx
With[{
ExportButton = Button["Export", export]
},
<div class="bg-white p-4 w-full h-full">
<div class="flex flex-row gap-x-2 justify-between">
<Model/>
<div class="mt-4"><ExportButton/></div>
</div>
</div>
]You can try to evaluate this notebook and check the result.
To export it as a mini app, follow:
Call Share from the top menu or click the icon and choose Mini app:
Then double-click on the exported file to open it with WLJS as a runtime.
User dialogs
Use MessageDialog for notifications:
MessageDialog["Operation completed!"];MessageDialog[Graphics[Disk[{0,0}, 1]]];In the case of lost context, i.e. MessageDialog is called by a timer, button click - provide "Notebook" as an option:
n = EvaluationNotebook[];
Button["Message", MessageDialog["Operation completed!", "Notebook"->n]]Use ChoiceDialog for user choices:
(* Simple OK/Cancel *)
result = ChoiceDialog["Proceed with operation?"];(* Custom choices *)
choice = ChoiceDialog["Select option:", {
"Option A" -> "a",
"Option B" -> "b"
}];ChoiceDialog is a blocking function. Avoid using it in timers like SetTimeout, external event handlers such as InputButton or Button, or within AsyncFunction. Use ChoiceDialogAsync instead:
n = EvaluationNotebook[];
Button["Message", Then[ChoiceDialogAsync["Proceed with operation?", "Notebook"->n], Function[choise,
Print[choise];
]]]Use Input or InputString for user input:
Input["Enter expression:", x^2]
InputString["Enter your name:"]Both of them are blocking functions. Again, use async versions in Button and other event handlers:
nb = EvaluationNotebook[];
Button["Enter expr", Then[InputAsync["Enter expression:", x^2, "Notebook"->nb],
Function[expr,
NotebookWrite[nb, expr];
]
]]System File dialogs
Use SystemDialogInput for file operations:
(* Open file *)
file = SystemDialogInput["FileOpen", {Null, {
"Images" -> {"*.png", "*.jpg"},
"All Files" -> {"*.*"}
}}];
(* Save file *)
path = SystemDialogInput["FileSave"];
(* Select directory *)
dir = SystemDialogInput["Directory"];System dialogs are no longer associated with notebooks; instead they require WindowObj to be provided, which specifies the parent application window:
w = CurrentWindow[];
SetTimeout[Then[SystemDialogInputAsync["FileSave", "Window"->w], Print], 1000];However, you do not have to provide "Window" if the dialog is called from a button click. The event context carries "Window" implicitly:
Button["Save", Then[SystemDialogInputAsync["FileSave"], Print]]Editor commands
You can send direct commands to the cells editor using FrontEditorSelected. This function is defined only on frontend, i.e. it has to be executed using FrontSubmit or FrontFetch.
To read the last selected text in a cell:
FrontFetch[FrontEditorSelected["Get"]]FrontFetch is a blocking function, please, consider FrontFetchAsyncOr using a button:
Button["Read selected", Then[FrontFetchAsync[FrontEditorSelected["Get"]], Print]]Read cursor position:
Button["Read selected", Then[FrontFetchAsync[FrontEditorSelected["Cursor"]], Print]]To insert or overwrite:
Button["Overwrite", FrontSubmit[FrontEditorSelected["Set", "1+1"]] ]Clipboard
Copying to clipboard
Use CopyToClipboard to programmatically copy:
CopyToClipboard[{1, 2, 3, 4, 5}];In the case of context loss, provide "Window" object as an option, i.e.
win = CurrentWindow[];
SetTimeout[CopyToClipboard[RandomWord[], "Window"->win]; Echo["Copied"], 1000];Use ClickToCopy for interactive copy buttons:
ClickToCopy["Click to copy this text"]
ClickToCopy["Copy Code", "Plot[Sin[x], {x, 0, 2Pi}]"]Pasting from clipboard
Use Paste to paste clipboard content as a new cell:
Paste[];Use PasteButton for interactive paste buttons:
PasteButton["Paste"]Directly read clipboard
ReadClipboard is symbol is defined on the frontend and allows to directly read clipboard from a given window. Since this is a frontend-only function, you need to use FrontFetch or FrontFetchAsync:
FrontFetch[ReadClipboard[]]or by a click on a button:
Button["Read", Then[FrontFetchAsync[ReadClipboard[]], Print]]"Window" object to FrontFetchAsync implicitly. This will not work automatically for timers, i.e. SetTimeout or events generated by parallel kernelsNotebook storage
Use NotebookStore to persist data within a notebook. This data survives export/import cycles, including Static HTML export.
NotebookStore[key_String ]this object represents a data stored with key in the notebook. Or excplicitly specify the notebook:
NotebookStore[nb, key_String ]Writting data
NotebookWrite[NotebookStore["myKey"], {1, 2, 3, 4, 5}];
NotebookWrite[NotebookStore["settings"], <|"theme" -> "dark", "fontSize" -> 14|>];Reading data
data = NotebookRead[NotebookStore["myKey"]];Note, that NotebookRead is a blocking function; use NotebookReadAsync if you need to read out the data by a timer or user actions.
Notebooks as command palette tools
We already mentioned our command palette in the very first guide. Every tool is a normal notebook, which is evaluated in the background and manipulates your working notebook, creating temporal cells whenever you call it from the palette.
~/Documents/WLJS Notebooks/User palette/What you should note before starting:
- Initialization cells are evaluated once
- All other cells are evaluated every time you call a tool (except text/markdown cells)
- All "global" symbols in tool notebook will be contextually isolated
CurrentWindowwill return the window of the host notebook (not tool's notebook!)NotebookDirectory,NotebookWrite,NotebookRead,NotebookFocusedCell,EvaluationNotebookwill be in the context of the host notebook (not tool's notebook!)Directory[]will return the directory of tool's notebook (not host!)- Icon can be set using a specially tagged WLX cell (see examples)
- Title of the tool has to be in the first markdown cell of the tool's notebook
- You do not need to evaluate any of cells in the tool's notebook
All manipulations on the notebook's data can be performed using all symbols we discussed in all sections of this guide.
Example-1: search text in a cell and print results
In this example we prompt a user to enter a string, then read the content of focused cell, find all matches and print them in the output cell.
First cell - tool name:
.md
# Search in cell
This tool finds a string in a focused cell and prints the results This example does not require any initialization cells, then we can skip this part and define our "action" cell:
focused = NotebookFocusedCell[];
content = NotebookRead[focused][[1]];
Then[InputStringAsync["Enter a string to search"], Function[string,
If[!TrueQ[StringLength[string] > 3], EchoLabel["Warning"]["String is too short"],
With[{cases = StringCases[content, (WordCharacter..|"")~~string~~(WordCharacter..|""),
IgnoreCase->True]},
NotebookWrite[NotebookLocationSpecifier[focused, "After"], ExpressionCell[TableForm[cases], "Output"]]
]
]]];As the last thing, we make a nice icon. This is a specially tagged WLX cell:
icon.wlx
Component[OptionsPattern[]] := With[{Title = OptionValue["Title"], Kbd = OptionValue["Shortcut"], UTag = OptionValue["Tag"], Btn = OptionValue["Button"]},
<li tabindex="-1" tag="{UTag}" class="list-none nooline group flex cursor-default select-none items-center rounded-md px-2 py-1 focus:bg-teal-500/25">
<svg class="h-4 ml-1 w-4 flex-none text-gray-900 dark:text-gray-400 text-opacity-40" fill="none" viewBox="0 0 24 24"><path d="M21 4H3M20 8H6M18 12H9M15 16H8M17 20H12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span class="ml-3 flex-auto truncate"><Title/></span><span class="ml-3 flex-none text-xs font-semibold text-gray-500">
<kbd class="font-sans"><Kbd/></kbd>
<button class="p-0.5 rounded hover:bg-gray-100 dark:hover:bg-gray-200" btag="{Btn}">
<svg class="w-4 h-4 text-gray-400" viewBox="0 0 24 24" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2.75C6.89137 2.75 2.75 6.89137 2.75 12C2.75 17.1086 6.89137 21.25 12 21.25C17.1086 21.25 21.25 17.1086 21.25 12C21.25 6.89137 17.1086 2.75 12 2.75ZM1.25 12C1.25 6.06294 6.06294 1.25 12 1.25C17.9371 1.25 22.75 6.06294 22.75 12C22.75 17.9371 17.9371 22.75 12 22.75C6.06294 22.75 1.25 17.9371 1.25 12ZM12 7.75C11.3787 7.75 10.875 8.25368 10.875 8.875C10.875 9.28921 10.5392 9.625 10.125 9.625C9.71079 9.625 9.375 9.28921 9.375 8.875C9.375 7.42525 10.5503 6.25 12 6.25C13.4497 6.25 14.625 7.42525 14.625 8.875C14.625 9.83834 14.1056 10.6796 13.3353 11.1354C13.1385 11.2518 12.9761 11.3789 12.8703 11.5036C12.7675 11.6246 12.75 11.7036 12.75 11.75V13C12.75 13.4142 12.4142 13.75 12 13.75C11.5858 13.75 11.25 13.4142 11.25 13V11.75C11.25 11.2441 11.4715 10.8336 11.7266 10.533C11.9786 10.236 12.2929 10.0092 12.5715 9.84439C12.9044 9.64739 13.125 9.28655 13.125 8.875C13.125 8.25368 12.6213 7.75 12 7.75ZM12 17C12.5523 17 13 16.5523 13 16C13 15.4477 12.5523 15 12 15C11.4477 15 11 15.4477 11 16C11 16.5523 11.4477 17 12 17Z" fill="currentColor"/>
</svg>
</button>
</span>
</li>
];
Options[Component] = {"Title"->"Example", "Shortcut"->"", "Tag"->"generic", "Button"->Null};
ComponentIf you are looking for some nice icons, visit the SVGRepo website. In this example it uses 2 SVG icons, one for a tool and another one for a question mark.
Now we save this notebook to the directory ~/Documents/WLJS Notebooks/User palette/ and restart the WLJS application.