Create a widget
How to build, package, and organize a widget for Appinapp.
use this prompt to create widget with AI you want to use:
this is widget schema: https://appinapp.mutawirr.uz/schema.md, create a widget of
<button></button>
Folder naming
Your widget folder name must end with
.widget — the app only recognizes folders with this suffix as widgets.Clock.widget
Layout
Every widget folder must contain these files at its root:
Clock.widget/
├── index.jsx # widget code (REQUIRED — the only entry point)
└── config.json # OPTIONAL declarative defaults (geometry, placement, behavior)
A widget is only recognized if the folder contains an
index.jsx. Without index file, the app skips the folder.index.jsx
The app loads
should export React component as default, plus optional config exports.
index.jsx, compiles JSX in place, and reads its exports. Youshould export React component as default, plus optional config exports.
Imports — limited environment
Only three modules resolve inside a widget:
import { useState } from "appinapp-react";
import { Clock } from "appinapp-icons";
import Scritto from "appinapp-animations";
System-audio frames and now-playing info arrive through dedicated
modules (never shell out — widgets must not depend on binaries the user
may not have installed):
modules (never shell out — widgets must not depend on binaries the user
may not have installed):
import { onAudioFrame } from "appinapp-audio";
import { onNowPlaying, onPlayers, togglePlayPause } from "appinapp-music";
Any other
npm deps. Bring your own logic in the file. Relative
(images, fonts,
absolute paths are rejected.
import throws Import not allowed: <name>. Nonpm deps. Bring your own logic in the file. Relative
./ asset imports(images, fonts,
.css) resolve to data URLs / text; .. segments andabsolute paths are rejected.
Minimal widget
import { useState } from "appinapp-react";
export default function Widget() {
return <div className="widget">Hello</div>;
}
Widget props
The app provides
output, error, run, setHeight, setWidth, setX, setY:export const command = "whoami"
export default function Widget({ output, error, run }) {
if (error) {
return <div style={{ color: "red" }}>{error}</div>;
}
return <div>{output}</div>;
}
| Prop | What it is |
|---|---|
output | stdout string from your command (see below) |
error | error if the command failed (null on success) |
run | async (cmd) => stdout — run a shell command in the widget dir (throws on failure) |
setHeight / setWidth / setX / setY | resize/move the live instance (persisted) |
| Export | Type | Effect |
|---|---|---|
default | Component | The widget UI (or export render) |
command | string | Shell command, stdout → output prop (read once at add-time; afterwards the per-instance copy wins) |
className | string | CSS injected as <style> |
index.jsx exports exactly these three things. Geometry, refresh interval,surfaces, z-index, workspaces, click-through, and glass live in
config.json, never in JS.The strings
refreshFreq, surface, allWorkspace(s),visible_on_all_workspaces, and colors do not exist anywhere in the app —never use them.
Config.json (declarative defaults)
Create a
config.json next to index.jsx to provide defaults that apply when users add your widget. All keys optional, canonical keys only — unknown keys are dropped:{
"renderMode": "board",
"zIndex": "top",
"height": 100,
"width": 100,
"x": 10,
"y": 20,
"onAllWorkspaces": true,
"clickThrough": false,
"refreshFrequency": 3000,
"liquidGlass": {
"enabled": false,
"variant": 0,
"corner_radius": 5,
"tint_color": "#FFFFFF80"
}
}
Tip:
refreshFrequency supports * expressions like "5 * 1000" (5 seconds) or "60 * 60 * 1000" (1 hour). 0/null/missing = run command once, no interval.Canonical keys (all optional):
renderMode—"board"|"window"zIndex—"bottom"|"normal"|"top"height,width,x,y— numbersonAllWorkspaces— booleanclickThrough— boolean (window surface only; mouse events pass through the widget)refreshFrequency— ms or*expression (e.g.,"5 * 1000","60 * 60 * 1000")liquidGlass— object withenabled,variant,corner_radius,tint_color
Note:
Note:
liquidGlass is entirely optional - omit for defaults (enabled: false, variant: 0, corner_radius: 5).Note:
command is NOT a config.json key — it comes from the index.jsx export and seeds the per-instance default at add-time. Gallery metadata (name, description, email) is not read from config.json by the app — do not add it here.Precedence (highest wins):
config.json(per-type defaults — read at widget add time)active_widgets.json(per-instance WidgetConfig — user overrides via WindowSettings)index.jsxcommandexport (seeds the per-instance command at add-time only)- Built-in defaults
config.json is read as defaults at add time — WindowSettings edits the per-instance copy afterwards, so editing the file later does not move existing instances (delete + re-add to re-seed).Full example
// index.jsx
export const command = "date +%H:%M:%S";
export const className = `
.clock { font: 600 28px monospace; color: #fff; padding: 12px; }
`;
export default function Clock({ output, error }) {
if (error) return <div className="clock">err</div>;
return <div className="clock">{output || "--:--:--"}</div>;
}
// config.json
{
"renderMode": "board",
"zIndex": "top",
"width": 200,
"height": 80,
"x": 40,
"y": 40,
"onAllWorkspaces": true,
"clickThrough": false,
"refreshFrequency": 1000,
"liquidGlass": {
"enabled": false,
"variant": 0,
"corner_radius": 5
}
}
screenshot.png
A preview image shown in the app gallery. Capture your widget running.
It's aspect ratio should be between
1.3 - 3:Forced UP to
2.25 (9/4) (because they're below the minimum):| Ratio | Value | What it is |
|---|---|---|
1/1 | 1.0 | Square |
4/5 | 0.8 | Instagram portrait |
9/16 | 0.56 | Vertical phone video/story |
3/4 | 0.75 | Portrait photo |
5/4 | 1.25 | Slightly wide print photo |
All of these become
1.3 — the function treats squares and anything portrait-oriented (taller than wide, or just barely wide) as "too narrow" and pushes them up.Left UNCHANGED (already inside
1.3–3):| Ratio | Value | What it is |
|---|---|---|
4/3 | 1.33 | Old TV / classic photo |
3/2 | 1.5 | 35mm film / DSLR photo |
16/9 | 1.78 | Widescreen video/monitor |
2/1 | 2.0 | Wide banner |
21/9 | 2.33 | Ultrawide monitor |
2.35/1 | 2.35 | Cinematic movie |
These pass through exactly as-is since they already fall in the
1.3–3 sweet spot.Forced DOWN to 3 (because they're above the maximum):
| Ratio | Value | What it is |
|---|---|---|
16/5 | 3.2 | Very wide banner |
4/1 | 4.0 | Extreme panorama |
32/9 | 3.56 | Super ultrawide monitor |
All of these become
3 — capped rather than left at their true, more extreme value.Bottom line: the only ratio "zone" that survives untouched is roughly between 4:3 and slightly beyond cinematic widescreen (1.3–3). Anything square or portrait gets pulled up to
2.25.README.md
Describe what the widget does and how to customize it. It will be shown to users when open
your widget. Follow the template below! Examples
your widget. Follow the template below! Examples
- Name
- Description
- Features
- How it works
- Customization
- Notes
- License