Widgets
A module can add buttons and indicators to the HUD, MiniCal, and BigCal, or replace one of Calendaria’s own indicators with markup of its own.
Widget points
Section titled “Widget points”| Point | Location |
|---|---|
hud.buttons.left | Left side of the HUD bar |
hud.buttons.right | Right side of the HUD bar |
hud.indicators | Indicator row of the HUD bar, MiniCal, and BigCal |
hud.tray | HUD time controls tray |
minical.sidebar | MiniCal sidebar |
bigcal.actions | BigCal action bar |
Read these from CALENDARIA.api.widgetPoints.
A point controls only where a widget renders. Any of the three widget types renders at any point, and registration does not reject a type and point combination.
Despite its name, hud.indicators is the only point that renders in more than one application. A widget registered there appears in the HUD indicator row, the MiniCal indicator row, and the BigCal header indicator row, and its render function receives 'hud', 'minical', or 'bigcal' to match. The MiniCal indicator row is drawn whenever a widget targets this point, even when no built-in indicator is enabled.
The HUD time controls tray is only built for users permitted to change the date and time. Widgets at hud.tray are absent for everyone else, so do not put anything a player must reach there. See Permissions.
Replaceable elements
Section titled “Replaceable elements”| Element ID | Description |
|---|---|
weather-indicator | Weather condition display |
season-indicator | Current season display |
era-indicator | Era/year display |
cycle-indicator | Cycle (zodiac, etc.) display |
Read these from CALENDARIA.api.replaceableElements. A replacement takes over that indicator wherever it appears, in the HUD, the MiniCal, and the BigCal alike.
Registering a widget
Section titled “Registering a widget”Button widget
Section titled “Button widget”Widgets registered during calendaria.ready appear on the first render of every Calendaria application.
Hooks.once('calendaria.ready', () => { CALENDARIA.api.registerWidget('my-module', { id: 'my-button', type: 'button', insertAt: 'hud.buttons.right', icon: 'fas fa-star', label: 'My Button', tooltip: 'Click to do something', onClick: () => { console.log('Button clicked!'); } });});registerWidget returns true on success and false when it refuses the widget, and logs a console warning naming the reason. It refuses a missing moduleId or config.id, a config carrying neither insertAt nor replaces, an insertAt or replaces that is not one of the recognized values, a moduleId.id that is already registered, and a built-in element another widget has already claimed. Replacements are first come: a second widget cannot take an element away from the first. There is no call that unregisters a widget once it has been accepted.
Indicator widget
Section titled “Indicator widget”An indicator renders from its icon, label, color, and tooltip properties:
CALENDARIA.api.registerWidget('my-module', { id: 'mana-indicator', type: 'indicator', insertAt: 'hud.indicators', icon: 'fas fa-gem', label: 'Mana: 50', color: '#3aa0ff', tooltip: 'Current mana', onAttach: (element) => { // Called when the widget is added to the DOM }});Custom widget
Section titled “Custom widget”A type: 'custom' widget supplies a render function. The function receives the location string ('hud', 'minical', or 'bigcal') and returns an HTML string, which Calendaria wraps in a <div class="calendaria-widget-custom">:
CALENDARIA.api.registerWidget('my-module', { id: 'mana-display', type: 'custom', insertAt: 'hud.indicators', render: (location) => { return ` <span class="indicator-icon"><i class="fas fa-gem"></i></span> <span class="indicator-label">Mana: 50</span> `; }, onAttach: (element) => { // Called when the widget is added to the DOM }});Replacement widget
Section titled “Replacement widget”A replacement that needs a render function must use type: 'custom'. The render function receives the location string and returns an HTML string:
CALENDARIA.api.registerWidget('my-module', { id: 'custom-weather', type: 'custom', replaces: 'weather-indicator', render: (location) => { const weather = CALENDARIA.api.getCurrentWeather(); return `<span>Custom: ${weather?.label || 'None'}</span>`; }});Set disabled: true on a replaces widget to hide the built-in element and render nothing in its place. It works for any widget type, and it is ignored on a widget registered with insertAt.
Widget configuration
Section titled “Widget configuration”Required properties
Section titled “Required properties”| Property | Type | Description |
|---|---|---|
id | string | Unique widget identifier within your module |
type | string | 'button', 'indicator', or 'custom' |
Insertion properties
Section titled “Insertion properties”One of these is required:
| Property | Type | Description |
|---|---|---|
insertAt | string | Widget point ID from widgetPoints |
replaces | string | Element ID from replaceableElements |
Button properties
Section titled “Button properties”| Property | Type | Description |
|---|---|---|
icon | string | function | FontAwesome icon class |
label | string | function | Button text. Rendered only when no icon is supplied, since an icon replaces the label rather than sitting beside it. |
tooltip | string | function | Hover tooltip text |
color | string | function | Icon color, as a CSS color value |
onClick | function | Click handler, receives the event |
Indicator properties
Section titled “Indicator properties”| Property | Type | Description |
|---|---|---|
icon | string | function | FontAwesome icon class |
label | string | function | Indicator text |
color | string | function | Color of the icon and the label, as a CSS color value |
tooltip | string | function | Hover tooltip text |
onClick | function | Click handler, receives the event. Also adds a clickable class that switches the cursor to a pointer. |
onAttach | function | Called with the DOM element when the widget is attached |
Custom properties
Section titled “Custom properties”| Property | Type | Description |
|---|---|---|
render | function | Receives the location string and returns an HTML string |
onClick | function | Click handler, receives the event. Bound to the wrapper, so a click anywhere in your markup reaches it. |
onAttach | function | Called with the DOM element when the widget is attached |
On button and indicator widgets, icon, label, color, and tooltip each take a function in place of a literal. Calendaria calls it every time the widget renders and uses what comes back, which is how a widget shows a value that changes. A custom widget builds its own markup instead, so these four are ignored there.
onAttach runs after every render of the application the widget sits in, not only the first, so attach listeners and read state there rather than assuming a single call. A widget at hud.indicators is attached separately in each application it appears in.
Managing widgets
Section titled “Managing widgets”// Get all widgets at a specific pointconst hudButtons = CALENDARIA.api.getRegisteredWidgets('hud.buttons.right');
// Get widget replacing a specific elementconst weatherWidget = CALENDARIA.api.getWidgetByReplacement('weather-indicator');
// Force all widgets to re-renderCALENDARIA.api.refreshWidgets();Widget hooks are documented in Hooks > Widget Hooks:
calendaria.widgetRegistered- Fired when a widget is registeredcalendaria.widgetsRefresh- Fired when widgets are refreshed
CSS styling
Section titled “CSS styling”Every widget carries a class built from its module ID, plus a structural class for its type. Buttons get calendaria-widget, indicators get calendaria-widget-indicator and wrap any label text in a .widget-label span, and custom widgets get the calendaria-widget-custom wrapper.
Button widgets also get a base class for the application they render in: btn in the HUD, sidebar-btn in the MiniCal sidebar, and none in the BigCal action bar. sidebar-btn is not the class the built-in MiniCal sidebar buttons use, so style MiniCal sidebar widgets yourself rather than expecting them to match.
/* Style all widgets from your module */.widget-my-module { /* Your styles */}
/* Style a specific widget */.widget-my-module[data-widget-id='my-module.my-button'] { /* Your styles */}The data-widget-id attribute holds the fully-qualified id (moduleId.id).
The color property is written to the element as a --widget-color custom property, which Calendaria’s own rules read for both the icon and the label. A widget that registers no color picks up whatever --widget-color a stylesheet sets, while a registered color lands inline and wins over that.
Example: complete module integration
Section titled “Example: complete module integration”Hooks.once('calendaria.ready', ({ api }) => { // Add a button to show party resources api.registerWidget('my-module', { id: 'party-resources', type: 'button', insertAt: 'hud.buttons.right', icon: 'fas fa-coins', tooltip: 'Party Resources', onClick: () => openResourceTracker() });
// Add a custom widget api.registerWidget('my-module', { id: 'party-gold', type: 'custom', insertAt: 'hud.indicators', render: (location) => { const gold = getPartyGold(); return ` <span class="indicator-icon"><i class="fas fa-coins" style="color: gold;"></i></span> <span class="indicator-label">${gold} gp</span> `; } });
// Update the widget when gold changes Hooks.on('updatePartyGold', () => { api.refreshWidgets(); });});