Widget API
Dashboard widgets are the most common way plugins surface content in Ghosty. A widget occupies a bounded region on the user's home screen and can be resized between three size presets. The Widget API provides utilities for querying dimensions, responding to resize events, and matching the host application's theme.
Size presets
Ghosty widgets use a grid-based layout with three size presets:
| Preset | Width (px) | Height (px) | Grid cells |
|---|---|---|---|
small | 160 | 160 | 1 x 1 |
medium | 340 | 160 | 2 x 1 |
large | 340 | 340 | 2 x 2 |
The default size is declared in plugin.yaml under views.widget.default_size.
Users can resize between the min_size and max_size boundaries set in the
manifest.
Design your widget to look good at all three sizes. Use relative units and flexbox/grid layouts so content adapts gracefully when the user resizes.
Querying dimensions
Use ghosty.widget.getSize() to query the current size preset and pixel
dimensions at any time.
ghosty.widget.getSize()Returns the current size preset and pixel dimensions of the widget container.
The returned object has this shape:
interface WidgetSize {
/** Current size preset */
preset: "small" | "medium" | "large";
/** Container width in CSS pixels */
width: number;
/** Container height in CSS pixels */
height: number;
}Example
import { ghosty } from "@ghosty/sdk";
const size = ghosty.widget.getSize();
if (size.preset === "small") {
renderCompactView();
} else {
renderDetailedView();
}Resize events
When the user resizes the widget, Ghosty fires a resize event. Subscribe to it
with ghosty.widget.onResize().
ghosty.widget.onResize(callback)Register a callback that fires whenever the widget is resized. Returns an unsubscribe function.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
callback | (size: WidgetSize) => void | Yes | Called with the new size information after each resize. |
Example
import { ghosty } from "@ghosty/sdk";
const unsubscribe = ghosty.widget.onResize((size) => {
console.log(`Resized to ${size.preset}: ${size.width}x${size.height}`);
// Re-render with the new dimensions
if (size.preset === "small") {
renderCompactView();
} else if (size.preset === "medium") {
renderMediumView();
} else {
renderFullView();
}
});
// Later, when cleaning up:
// unsubscribe();The resize callback may fire multiple times during an animated resize transition. If your re-render logic is expensive, debounce the callback to avoid jank.
Theme integration
Widgets should match the Ghosty host application's theme. The Widget API provides access to the current theme and fires events when the user toggles between light and dark mode.
ghosty.widget.getTheme()Returns the current theme configuration including mode and colour tokens.
The returned object:
interface WidgetTheme {
/** Current mode */
mode: "light" | "dark";
/** Resolved colour tokens (CSS custom property values) */
colors: {
background: string;
foreground: string;
primary: string;
primaryForeground: string;
muted: string;
mutedForeground: string;
border: string;
card: string;
cardForeground: string;
};
}ghosty.widget.onThemeChange(callback)Register a callback that fires when the host theme changes. Returns an unsubscribe function.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
callback | (theme: WidgetTheme) => void | Yes | Called with the new theme after a theme change. |
Theme example
import { ghosty } from "@ghosty/sdk";
function applyTheme(theme: WidgetTheme): void {
const root = document.documentElement;
root.style.setProperty("--bg", theme.colors.background);
root.style.setProperty("--fg", theme.colors.foreground);
root.style.setProperty("--primary", theme.colors.primary);
root.classList.toggle("dark", theme.mode === "dark");
}
// Apply initial theme
applyTheme(ghosty.widget.getTheme());
// React to changes
ghosty.widget.onThemeChange(applyTheme);Widget container
The widget's root DOM element is passed to your render function. This is a
standard HTMLDivElement with the following guarantees:
- It has
overflow: hiddenset by the host. - It is sized to match the current size preset.
- It has
position: relativeso you can use absolute positioning inside it. - It has a transparent background by default. Use the theme colours to set your own background.
Render function signature
Your widget entry file must export a render function:
export function render(container: HTMLElement): void {
container.innerHTML = `
<div class="widget-content">
<h3>My Widget</h3>
<p>Content goes here.</p>
</div>
`;
}You can use any rendering approach: raw DOM manipulation, a framework like Preact or Lit, or simple template strings.
Keep widget bundle sizes small. The recommended maximum is 100 KB gzipped. Consider using lightweight alternatives like Preact (3 KB) instead of React (40 KB) for widget views.
Requesting fullscreen
If your plugin also declares a fullscreen view, you can transition to it from the widget with a single call:
import { ghosty } from "@ghosty/sdk";
document.getElementById("expand-btn")?.addEventListener("click", () => {
ghosty.navigate.fullscreen();
});This triggers the onHidden hook on the widget and the onVisible hook on
the fullscreen view.
Auto-refresh
If refresh_interval is set in the manifest, the Ghosty runtime periodically
calls a refresh function exported from your widget entry file:
export function refresh(container: HTMLElement): void {
// Called every `refresh_interval` seconds
// Re-fetch data and update the UI
fetchLatestData().then((data) => {
updateUI(container, data);
});
}The refresh function receives the same container element as render. It is
only called while the widget is visible.
Users can also manually trigger a refresh by long-pressing the widget and
selecting "Refresh" from the context menu. This calls the same refresh
function.
Best practices
- Design for all three sizes. Test your widget at small, medium, and large. Avoid fixed pixel dimensions.
- Use the theme colours. Never hard-code light or dark mode colours. Use the theme tokens so your widget adapts automatically.
- Keep the initial render fast. Show a skeleton or placeholder immediately and load data asynchronously.
- Clean up event listeners. Unsubscribe from resize and theme change events
in your
onDestroyhook. - Minimise bundle size. Widgets are loaded on the user's home screen. Keep your JavaScript payload lean.
- Handle errors visually. If data loading fails, display a friendly error message inside the widget instead of leaving it blank.