Widgets
Egern supports iOS Widgets, allowing users to display custom content on the Home Screen and Lock Screen. Widgets are rendered from a JSON-based DSL description generated by JavaScript scripts.
Widget Configuration
Define widgets in the widgets field of the main configuration file:
-
name (string), required
The widget name, must be unique.
-
script_name (string), optional
The name of an associated generic script. Defaults to using a script with the same name as the widget.
-
env (object), optional
Environment variables (key-value pairs) passed to the script. See Environment Variables for details.
Configuration Example
scriptings:
- generic:
name: "weather-widget"
script_url: "https://example.com/scripts/weather.js"
timeout: 20
- generic:
name: "net-status-script"
script_url: "https://example.com/scripts/net-status.js"
timeout: 20
widgets:
# name matches script name, no need to set script_name
- name: "weather-widget"
env:
CITY: "Shanghai"
UNIT: "celsius"
# name differs from script name, use script_name to specify the associated script
- name: "network-monitor"
script_name: "net-status-script"
Widget DSL
A widget script is a generic type script that returns a JSON-based DSL description via return. The DSL uses a tree structure composed of nested elements.
Script Context
The following properties are available on the ctx object during widget script execution:
| Variable | Description |
|---|---|
ctx.widgetFamily | Widget size family |
ctx.env | Environment variables as key-value pairs |
Widget Families:
| Value | Description |
|---|---|
systemSmall | Home Screen small |
systemMedium | Home Screen medium |
systemLarge | Home Screen large |
systemExtraLarge | Home Screen extra large (iPad) |
accessoryCircular | Lock Screen circular |
accessoryRectangular | Lock Screen rectangular |
accessoryInline | Lock Screen inline |
Element Types (type)
| Value | Description |
|---|---|
widget | Root container, must be the outermost element. Uses vertical layout internally |
stack | Flex container, supports horizontal / vertical direction |
text | Text |
image | Image (SF Symbol or Base64 image) |
spacer | Flexible / fixed spacing |
date | Real-time date/time display (automatically updated by the system) |
Common Properties
| Property | Type | Applies to | Description |
|---|---|---|---|
url | string | widget, stack, text, image, date | URL to open on tap (deep link or web URL) |
opacity | number | text, image, date | Opacity, 0.0 – 1.0, default 1.0 |
width | number | stack, image | Element width, 0 or unset means no constraint |
height | number | stack, image | Element height, 0 or unset means no constraint |
flex | number | All elements | Flex ratio. Distributes remaining space proportionally among flex children in the parent container |
padding | number | [top, right, bottom, left] | widget, stack | Inner padding. A single number applies equally to all sides; an array specifies each side in CSS clockwise order |
gap | number | widget, stack | Spacing between children, default 0 |
backgroundColor | Color | widget, stack | Background color |
backgroundGradient | Gradient | widget, stack | Background gradient, takes priority over backgroundColor |
backgroundImage | string | widget, stack | Background image data URI (e.g. "data:image/png;base64,..."), highest priority |
borderRadius | number | "auto" | stack, image | Corner radius. Set to "auto" to automatically match the widget container's corner shape |
borderWidth | number | stack, image | Border width |
borderColor | Color | stack, image | Border color |
shadowColor | Color | text, image, date, stack | Shadow color |
shadowRadius | number | text, image, date, stack | Shadow blur radius (must be set for shadow to take effect) |
shadowOffset | Point | text, image, date, stack | Shadow offset {x, y}, default {0, 0} |
widget (Root Container)
The root container defaults to vertical layout, with children arranged top-to-bottom and aligned to the top-left.
| Property | Type | Description |
|---|---|---|
refreshAfter | string | ISO 8601 time indicating when the widget should refresh |
{
"type": "widget",
"children": [ ... ],
"gap": 8,
"padding": 16,
"backgroundColor": "#1A1A2E"
}
stack (Flex Container)
| Property | Type | Values | Description |
|---|---|---|---|
direction | string | "row", "column" | Layout direction, default "row" |
alignItems | string | See table below | Cross-axis alignment, default "center" |
children | [Element] | — | Array of child elements |
alignItems values:
| Value | Meaning in row stack | Meaning in column stack |
|---|---|---|
"start" | Children align to top | Children align to left |
"end" | Children align to bottom | Children align to right |
"center" | Children vertically centered | Children horizontally centered |
{
"type": "stack",
"direction": "row",
"alignItems": "center",
"gap": 6,
"children": [
{"type": "text", "text": "CPU"},
{"type": "spacer"},
{"type": "text", "text": "42%"}
]
}
text
| Property | Type | Description |
|---|---|---|
text | string | Text content to display, supports \n for line breaks |
font | Font | Font configuration |
textColor | Color | Text color, defaults to the system primary color |
textAlign | string | Text alignment: "left" (default), "center", "right" |
maxLines | number | Maximum number of lines |
minScale | number | Minimum text scale factor (0.0 – 1.0), used for adaptive shrinking |
{
"type": "text",
"text": "Hello, Widget!",
"font": {"size": "title2", "weight": "semibold"},
"textColor": "#FFFFFF",
"maxLines": 1,
"minScale": 0.5
}
image
The src property specifies the image source, supporting two URI schemes:
- SF Symbol —
sf-symbol:<name>, e.g."sf-symbol:wifi" - Base64 image —
data:<mime>;base64,<data>, e.g."data:image/png;base64,iVBORw0KGgo..."
| Property | Type | Description |
|---|---|---|
src | string | Image source URI |
color | Color | SF Symbol tint color (only applies to SF Symbols) |
resizeMode | string | "contain" (default) or "cover" |
resizable | boolean | Whether resizable. Set to false to use original size, default true (auto-enabled when width/height are set) |
When
srcis not provided or the URI cannot be parsed, a placeholder icon will be displayed.
{
"type": "image",
"src": "sf-symbol:wifi",
"width": 16,
"height": 16,
"color": "#FFFFFF"
}
{
"type": "image",
"src": "data:image/png;base64,iVBORw0KGgo...",
"width": 40,
"height": 40,
"resizeMode": "cover",
"borderRadius": 8
}
spacer
| Property | Type | Description |
|---|---|---|
length | number | Fixed length. When omitted, acts as a flexible spacer that fills remaining space |
{"type": "spacer"}
{"type": "spacer", "length": 10}
date
The system updates the date display in real time without requiring a widget refresh.
| Property | Type | Description |
|---|---|---|
date | string | ISO 8601 date string, e.g. "2026-03-04T12:00:00Z" |
format | string | Display style, see table below. Default "date" |
font | Font | Font configuration |
textColor | Color | Text color |
textAlign | string | Text alignment: "left" (default), "center", "right" |
maxLines | number | Maximum number of lines |
minScale | number | Minimum scale factor |
format values:
| Value | Description | Example Output |
|---|---|---|
"date" | Date | March 4, 2026 |
"time" | Time | 12:00 PM |
"relative" | Relative time | 2 hours ago |
"offset" | Offset | +2 hours |
"timer" | Timer | 2:30:15 |
{
"type": "date",
"date": "2026-03-04T12:00:00Z",
"format": "relative",
"font": {"size": "caption1", "weight": "medium"},
"textColor": "#FFFFFFDD"
}
Compound Type Definitions
Color
Colors support two modes:
Fixed color — use a string directly:
"textColor": "#FF5733"
"textColor": "#FF573380"
"textColor": "rgba(255, 87, 51, 1.0)"
Adaptive color — automatically switches between light/dark mode:
"textColor": {"light": "#000000", "dark": "#FFFFFF"}
Supported color formats:
| Format | Example | Description |
|---|---|---|
| 6-digit Hex | #RRGGBB | Opaque color |
| 8-digit Hex | #RRGGBBAA | With alpha (last two digits) |
| rgba() | rgba(R, G, B, A) | R/G/B: 0–255, A: 0.0–1.0 |
Font
{"size": "headline", "weight": "bold"}
{"size": 14, "weight": "bold"}
{"size": 14, "weight": "bold", "family": "Menlo"}
All fields are optional. When omitted, defaults to body style.
size: Supports semantic style names (string, dynamically scales with system font size) or exact numeric values (number).
| Semantic Style | Default Size |
|---|---|
"largeTitle" | 34 |
"title" | 28 |
"title2" | 22 |
"title3" | 20 |
"headline" | 17 |
"body" | 17 |
"callout" | 16 |
"subheadline" | 15 |
"footnote" | 13 |
"caption1" | 12 |
"caption2" | 11 |
weight: "ultraLight", "thin", "light", "regular", "medium", "semibold", "bold", "heavy", "black"
family: Custom font name (e.g. "Menlo"). When using family, you must also specify size to determine the font size.
Gradient
{
"type": "linear",
"colors": ["#1A1A2E", "#16213E", "#0F3460"],
"stops": [0, 0.5, 1.0],
"startPoint": {"x": 0, "y": 0},
"endPoint": {"x": 1, "y": 1}
}
type: "linear" (default), "radial", "angular"
Common properties:
| Property | Type | Description |
|---|---|---|
colors | [Color] | Array of gradient colors (required) |
stops | [number] | Position of each color 0.0–1.0, count must match colors |
Linear-specific properties:
| Property | Type | Default | Description |
|---|---|---|---|
startPoint | Point | {x: 0, y: 0} | Start point (top-left) |
endPoint | Point | {x: 1, y: 1} | End point (bottom-right) |
Radial-specific properties:
| Property | Type | Default | Description |
|---|---|---|---|
center | Point | {x: 0.5, y: 0.5} | Center point |
startRadius | number | 0 | Start radius |
endRadius | number | 100 | End radius |
Angular-specific properties:
| Property | Type | Default | Description |
|---|---|---|---|
center | Point | {x: 0.5, y: 0.5} | Center point |
startAngle | number | 0 | Start angle (degrees) |
endAngle | number | 360 | End angle (degrees) |
Point
{"x": 0.5, "y": 0.5}
flex (Flex Layout)
When child elements have the flex property set, the parent container (widget or stack) distributes remaining space proportionally among those children. Children without flex retain their natural size.
Equal distribution (1:1):
{
"type": "stack",
"direction": "row",
"gap": 8,
"children": [
{"type": "text", "text": "Left", "flex": 1},
{"type": "text", "text": "Right", "flex": 1}
]
}
Proportional layout (1:2):
{
"type": "stack",
"direction": "row",
"gap": 8,
"children": [
{"type": "text", "text": "Sidebar", "flex": 1},
{"type": "text", "text": "Content", "flex": 2}
]
}
Fixed + flexible mix:
{
"type": "stack",
"direction": "row",
"gap": 8,
"children": [
{"type": "image", "src": "sf-symbol:star.fill", "width": 20, "height": 20},
{"type": "text", "text": "Fills remaining space", "flex": 1}
]
}
Padding
16
[8, 12]
[8, 12, 8, 12]
A single number applies equally to all sides; arrays support the following formats:
| Elements | Format | Description |
|---|---|---|
| 2 | [vertical, horizontal] | Top/bottom and left/right equal spacing |
| 4 | [top, right, bottom, left] | CSS clockwise order |
Full Example
Configuration File
scriptings:
- generic:
name: "server-status"
script_url: "https://example.com/scripts/server-status.js"
timeout: 20
env:
API_URL: "https://api.example.com/status"
widgets:
- name: "server-status"
env:
REGION: "Asia"
Widget Script
export default async function(ctx) {
const apiUrl = ctx.env.API_URL;
const region = ctx.env.REGION;
let result;
try {
const resp = await ctx.http.get(apiUrl + '?region=' + region);
result = await resp.json();
} catch (e) {
return {
type: 'widget',
padding: 16,
children: [{
type: 'text',
text: 'Failed to load',
textColor: '#FF3B30'
}]
};
}
// Adjust layout based on widget family
if (ctx.widgetFamily === 'accessoryRectangular') {
return {
type: 'widget',
children: [{
type: 'text',
text: result.name + ': ' + result.status,
font: { size: 'headline', weight: 'semibold' }
}]
};
}
return {
type: 'widget',
backgroundGradient: {
type: 'linear',
colors: ['#1a1a2e', '#16213e'],
startPoint: { x: 0, y: 0 },
endPoint: { x: 1, y: 1 }
},
padding: 16,
children: [
{
type: 'stack',
direction: 'row',
alignItems: 'center',
gap: 8,
children: [
{
type: 'image',
src: 'sf-symbol:server.rack',
color: '#007AFF',
width: 20,
height: 20
},
{
type: 'text',
text: result.name,
font: { size: 'headline', weight: 'bold' },
textColor: '#FFFFFF'
}
]
},
{ type: 'spacer' },
{
type: 'stack',
direction: 'column',
gap: 4,
children: [
{
type: 'text',
text: 'Region: ' + region,
font: { size: 'subheadline' },
textColor: { light: '#666666', dark: '#AAAAAA' }
},
{
type: 'text',
text: 'Status: ' + result.status,
font: { size: 'subheadline', weight: 'semibold' },
textColor: result.status === 'OK' ? '#34C759' : '#FF3B30'
}
]
},
{
type: 'date',
date: new Date().toISOString(),
format: 'relative',
font: { size: 'caption2' },
textColor: '#888888'
}
]
};
}
Widgets in Modules
Module files can also include a widgets field to define widgets. Widgets defined in a module become active when the module is enabled.
name: "Network Monitor Module"
description: "Display network status in widgets"
author: "module-author"
scriptings:
- generic:
name: "net-monitor"
script_url: "https://example.com/scripts/net-monitor.js"
timeout: 20
widgets:
- name: "net-monitor"
When referencing modules in the main configuration, you can pass environment variables to the module's widgets via env:
modules:
- url: "https://example.com/net-monitor.yaml"
enabled: true
env:
REFRESH_INTERVAL: "300"
ALERT_THRESHOLD: "90"