> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cskn.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Widgets

> Let owners show a slice of your app's content on their profile page.

A widget surfaces part of an owner's content from your app on their cskn profile: their latest posts, a stat, a progress bar, a button. The server resolves each widget into a small declarative payload, and every surface (web, mobile, desktop, TV) renders that same payload, so a widget written once appears everywhere.

The SDK covers two jobs:

* **Settings:** your app's settings screen lets the owner choose which widgets to show, where and how big.
* **Rendering:** a profile page fetches the widgets a visitor may see and draws them. @cskn/ui has the [renderers](/ui/widgets).

## Settings screen

If you use @cskn/ui, drop in [`<WidgetSettings />`](/ui/settings-panels#widgetsettings). Otherwise `useWidgetSettings` gives you the same behavior to render in your own design:

```tsx theme={null}
import { useWidgetSettings } from '@cskn/sdk';

function MyWidgetSettings({ locale }: { locale: string }) {
  const w = useWidgetSettings(undefined, { locale });

  if (w.isLoading) return <Spinner />;

  return (
    <section>
      {w.blockedBy === 'private' && <p>Your app is private, so visitors won't see these widgets.</p>}
      {w.definitions.map((def) => (
        <label key={def.id}>
          <input
            type="checkbox"
            checked={w.draft[def.id]?.enabled ?? false}
            onChange={(e) => w.toggle(def.id, e.target.checked)}
          />
          {def.name}
        </label>
      ))}
      <button onClick={w.save} disabled={w.isSaving}>{w.isSaved ? 'Saved' : 'Save'}</button>
    </section>
  );
}
```

<ResponseField name="definitions" type="WidgetDefinition[]">
  What your app offers, translated to `locale` when the catalog has a translation.
</ResponseField>

<ResponseField name="draft" type="Record<string, WidgetConfig>">
  The owner's unsaved choices, keyed by widget id. Widgets added since they last saved appear switched off.
</ResponseField>

<ResponseField name="blockedBy" type="'not-activated' | 'private' | 'followers-only' | null">
  Set when at least one widget is on but something else hides it from visitors. Supply your own wording for each.
</ResponseField>

<ResponseField name="toggle / setSize / setSlot / setShowHeader / setShowCard / setTitle / setOption" type="(id, value) => void">
  Edit the draft. `setOption(id, key, value)` sets a widget-specific option.
</ResponseField>

<ResponseField name="save / isSaving / isSaved / saveError" type="…">
  Save the draft. `saveError` is `'not-activated'` when the app is not on the owner's profile, otherwise `'failed'`.
</ResponseField>

### Sizes and slots

| Field | Values |
| - | - |
| `size` | `sm`, `md`, `lg`, `full` |
| `slot` | `bio` (under the bio, most prominent), `about` (beside skills and links), `content` (main column, default), `footer`, `action` (the button row beside Follow) |

The `action` slot holds buttons, not cards: only an `action`-layout widget may sit there, and it may sit nowhere else. `slotsForLayout(layout)` returns the slots to offer for a widget. A theme without a given region falls back to `content`, so an enabled widget is never lost.

## Rendering a profile's widgets

```tsx theme={null}
import { useUserWidgets, groupWidgetsBySlot } from '@cskn/sdk';
import { WidgetGrid, WidgetActions } from '@cskn/ui';

function ProfileWidgets({ username }: { username: string }) {
  const { data } = useUserWidgets(username);
  const slots = groupWidgetsBySlot(data?.widgets);

  return (
    <>
      <WidgetActions widgets={slots.action} />
      <WidgetGrid widgets={slots.content} />
    </>
  );
}
```

`groupWidgetsBySlot` always returns every slot key, possibly empty, and sends unknown slots to `content`.

## Content layouts

Each `WidgetInstance.content` has a `layout`: `list`, `grid`, `media`, `gallery`, `stat`, `text`, `progress`, `action`, or `custom`. The `custom` layout carries a node tree built from a closed vocabulary (`flex` and `grid` containers; `text`, `image`, `avatar`, `video`, `icon`, `button`, `badge`, `progress`, `divider`, `spacer` leaves). There is no HTML, no style strings and no script, so the same tree is safe to render on someone else's profile on any surface.

## Lower-level API

| Export | Purpose |
| - | - |
| `useWidgetCatalog(appSlug?)` | Your app's definitions plus the owner's saved configs. |
| `useSaveWidgetConfig(appSlug?)` | Mutation that replaces the owner's selection. |
| `getUserWidgets`, `getWidgetCatalog`, `saveWidgetConfig` | Plain functions. |
| `localizeWidget(def, locale)` | Resolve a definition's name and description for a locale (`pt-BR`, then `pt`, then English). |
| `WIDGET_SLOTS`, `CONTENT_WIDGET_SLOTS` | Slot lists. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.