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 index.jsx, compiles JSX in place, and reads its exports. You
should 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):
import { onAudioFrame } from "appinapp-audio";
import { onNowPlaying, onPlayers, togglePlayPause } from "appinapp-music";
Any other import throws Import not allowed: <name>. No
npm deps. Bring your own logic in the file. Relative ./ asset imports
(images, fonts, .css) resolve to data URLs / text; .. segments and
absolute 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>;
}
PropWhat it is
outputstdout string from your command (see below)
errorerror if the command failed (null on success)
runasync (cmd) => stdout — run a shell command in the widget dir (throws on failure)
setHeight / setWidth / setX / setYresize/move the live instance (persisted)
ExportTypeEffect
defaultComponentThe widget UI (or export render)
commandstringShell command, stdout → output prop (read once at add-time; afterwards the per-instance copy wins)
classNamestringCSS 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 — numbers
  • onAllWorkspaces — boolean
  • clickThrough — boolean (window surface only; mouse events pass through the widget)
  • refreshFrequency — ms or * expression (e.g., "5 * 1000", "60 * 60 * 1000")
  • liquidGlass — object with enabled, variant, corner_radius, tint_color
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.jsx command export (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):
RatioValueWhat it is
1/11.0Square
4/50.8Instagram portrait
9/160.56Vertical phone video/story
3/40.75Portrait photo
5/41.25Slightly 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):
RatioValueWhat it is
4/31.33Old TV / classic photo
3/21.535mm film / DSLR photo
16/91.78Widescreen video/monitor
2/12.0Wide banner
21/92.33Ultrawide monitor
2.35/12.35Cinematic 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):
RatioValueWhat it is
16/53.2Very wide banner
4/14.0Extreme panorama
32/93.56Super 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
  • Name
  • Description
  • Features
  • How it works
  • Customization
  • Notes
  • License