Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
bilalmk avatar

Building Chat Widgets

  • 14 installs
  • 1 repo stars
  • Updated January 27, 2026
  • bilalmk/todo_correct

building-chat-widgets is a skill for building interactive AI chat widgets with buttons, forms, client/server action handlers, and entity tagging.

About

building-chat-widgets is a skill for building interactive AI chat widgets with buttons, forms, and bidirectional actions. It defines widget templates, wires client-handled actions (navigation, local state) and server-handled actions (data mutation, widget replacement), and supports entity tagging with @mentions. A developer uses it to create agentic UIs where users click widgets that the frontend or backend handles. It is not meant for simple text-only chat without interactive elements.

  • Builds interactive AI chat widgets with buttons, forms, and actions
  • Distinguishes client-handled and server-handled widget actions
  • Supports entity tagging (@mentions) and widget replacement

Building Chat Widgets by the numbers

  • 14 all-time installs (skills.sh)
  • Ranked #11,296 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
  • Data as of Jul 28, 2026 (Skillselion catalog sync)
At a glance

building-chat-widgets capabilities & compatibility

Requires an LLM/agent API key for the chat backend

Capabilities
widget building · agent integration · chat ui building
Use cases
frontend · ui design · orchestration
Pricing
Bring your own API key
From the docs

What building-chat-widgets says it does

Build interactive AI chat widgets with buttons, forms, and bidirectional actions.
SKILL.md
Actions that mutate data, update widgets, or require backend processing:
SKILL.md
Allow users to @mention entities in messages:
SKILL.md
npx skills add https://github.com/bilalmk/todo_correct --skill building-chat-widgets

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs14
repo stars1
Last updatedJanuary 27, 2026
Repositorybilalmk/todo_correct

What it does

Build interactive AI chat widgets with client/server action handlers and @mention entity tagging.

Who is it for?

Adding interactive clickable widgets and @mentions to an AI chat

Skip if: Simple text-only chat without interactive elements

When should I use this skill?

Creating agentic UIs with clickable widgets, entity tagging, or server-handled widget actions

What you get

Interactive chat widgets with wired client/server actions and entity @mentions.

  • Widget templates
  • Client and server action handlers
  • @mention entity tagging

By the numbers

  • Two action handler types: client and server
  • 5-step widget lifecycle

Files

SKILL.mdMarkdownGitHub ↗

Building Chat Widgets

Create interactive widgets for AI chat with actions and entity tagging.

Quick Start

const chatkit = useChatKit({
  api: { url: API_URL, domainKey: DOMAIN_KEY },

  widgets: {
    onAction: async (action, widgetItem) => {
      if (action.type === "view_details") {
        navigate(`/details/${action.payload.id}`);
      }
    },
  },
});

---

Action Handler Types

HandlerDefined InProcessed ByUse Case
"client"Widget templateFrontend onActionNavigation, local state
"server"Widget templateBackend action()Data mutation, widget replacement

---

Widget Lifecycle

1. Agent tool generates widget → yield WidgetItem
2. Widget renders in chat with action buttons
3. User clicks action → action dispatched
4. Handler processes action:
   - client: onAction callback in frontend
   - server: action() method in ChatKitServer
5. Optional: Widget replaced with updated state

---

Core Patterns

1. Widget Templates

Define reusable widget layouts with dynamic data:

{
  "type": "ListView",
  "children": [
    {
      "type": "ListViewItem",
      "key": "item-1",
      "onClickAction": {
        "type": "item.select",
        "handler": "client",
        "payload": { "itemId": "item-1" }
      },
      "children": [
        {
          "type": "Row",
          "gap": 3,
          "children": [
            { "type": "Icon", "name": "check", "color": "success" },
            { "type": "Text", "value": "Item title", "weight": "semibold" }
          ]
        }
      ]
    }
  ]
}

2. Client-Handled Actions

Actions that update local state, navigate, or send follow-up messages:

Widget Definition:

{
  "type": "Button",
  "label": "View Article",
  "onClickAction": {
    "type": "open_article",
    "handler": "client",
    "payload": { "id": "article-123" }
  }
}

Frontend Handler:

const chatkit = useChatKit({
  api: { url: API_URL, domainKey: DOMAIN_KEY },

  widgets: {
    onAction: async (action, widgetItem) => {
      switch (action.type) {
        case "open_article":
          navigate(`/article/${action.payload?.id}`);
          break;

        case "more_suggestions":
          await chatkit.sendUserMessage({ text: "More suggestions, please" });
          break;

        case "select_option":
          setSelectedOption(action.payload?.optionId);
          break;
      }
    },
  },
});

3. Server-Handled Actions

Actions that mutate data, update widgets, or require backend processing:

Widget Definition:

{
  "type": "ListViewItem",
  "onClickAction": {
    "type": "line.select",
    "handler": "server",
    "payload": { "id": "blue-line" }
  }
}

Backend Handler:

from chatkit.types import (
    Action, WidgetItem, ThreadItemReplacedEvent,
    ThreadItemDoneEvent, AssistantMessageItem, ClientEffectEvent,
)

class MyServer(ChatKitServer[dict]):

    async def action(
        self,
        thread: ThreadMetadata,
        action: Action[str, Any],
        sender: WidgetItem | None,
        context: RequestContext,  # Note: Already RequestContext, not dict
    ) -> AsyncIterator[ThreadStreamEvent]:

        if action.type == "line.select":
            line_id = action.payload["id"]  # Use .payload, not .arguments

            # 1. Update widget with selection
            updated_widget = build_selector_widget(selected=line_id)
            yield ThreadItemReplacedEvent(
                item=sender.model_copy(update={"widget": updated_widget})
            )

            # 2. Stream assistant message
            yield ThreadItemDoneEvent(
                item=AssistantMessageItem(
                    id=self.store.generate_item_id("msg", thread, context),
                    thread_id=thread.id,
                    created_at=datetime.now(),
                    content=[{"text": f"Selected {line_id}"}],
                )
            )

            # 3. Trigger client effect
            yield ClientEffectEvent(
                name="selection_changed",
                data={"lineId": line_id},
            )

4. Entity Tagging (@mentions)

Allow users to @mention entities in messages:

const chatkit = useChatKit({
  api: { url: API_URL, domainKey: DOMAIN_KEY },

  entities: {
    onTagSearch: async (query: string): Promise<Entity[]> => {
      const results = await fetch(`/api/search?q=${query}`).then(r => r.json());

      return results.map((item) => ({
        id: item.id,
        title: item.name,
        icon: item.type === "person" ? "profile" : "document",
        group: item.type === "People" ? "People" : "Articles",
        interactive: true,
        data: { type: item.type, article_id: item.id },
      }));
    },

    onClick: (entity: Entity) => {
      if (entity.data?.article_id) {
        navigate(`/article/${entity.data.article_id}`);
      }
    },
  },
});

5. Composer Tools (Mode Selection)

Let users select different AI modes from the composer:

const TOOL_CHOICES = [
  {
    id: "general",
    label: "Chat",
    icon: "sparkle",
    placeholderOverride: "Ask anything...",
    pinned: true,
  },
  {
    id: "event_finder",
    label: "Find Events",
    icon: "calendar",
    placeholderOverride: "What events are you looking for?",
    pinned: true,
  },
];

const chatkit = useChatKit({
  api: { url: API_URL, domainKey: DOMAIN_KEY },
  composer: {
    placeholder: "What would you like to do?",
    tools: TOOL_CHOICES,
  },
});

Backend Routing:

async def respond(self, thread, item, context):
    tool_choice = context.metadata.get("tool_choice")

    if tool_choice == "event_finder":
        agent = self.event_finder_agent
    else:
        agent = self.general_agent

    result = Runner.run_streamed(agent, input_items)
    async for event in stream_agent_response(context, result):
        yield event

---

Widget Component Reference

Layout Components

ComponentPropsDescription
ListViewchildrenScrollable list container
ListViewItemkey, onClickAction, childrenClickable list item
Rowgap, align, justify, childrenHorizontal flex
Colgap, padding, childrenVertical flex
Boxsize, radius, background, paddingStyled container

Content Components

ComponentPropsDescription
Textvalue, size, weight, colorText display
Titlevalue, size, weightHeading text
Imagesrc, alt, width, heightImage display
Iconname, size, colorIcon from set

Interactive Components

ComponentPropsDescription
Buttonlabel, variant, onClickActionClickable button

---

Critical Implementation Details

Action Object Structure

IMPORTANT: Use action.payload, NOT action.arguments:

# WRONG - Will cause AttributeError
action.arguments

# CORRECT
action.payload

Context Parameter

The context parameter is RequestContext, not dict:

# WRONG - Tries to wrap RequestContext
request_context = RequestContext(metadata=context)

# CORRECT - Use directly
user_id = context.user_id

UserMessageItem Required Fields

When creating synthetic user messages:

from chatkit.types import UserMessageItem, UserMessageTextContent

# Include ALL required fields
synthetic_message = UserMessageItem(
    id=self.store.generate_item_id("message", thread, context),
    thread_id=thread.id,
    created_at=datetime.now(),
    content=[UserMessageTextContent(type="input_text", text=message_text)],
    inference_options={},
)

---

Anti-Patterns

1. Mixing handlers - Don't handle same action in both client and server 2. Missing payload - Always include data in action payload 3. Using action.arguments - Use action.payload 4. Wrapping RequestContext - Context is already RequestContext 5. Missing UserMessageItem fields - Include id, thread_id, created_at 6. Wrong content type - Use type="input_text" for user messages

---

Verification

Run: python3 scripts/verify.py

Expected: ✓ building-chat-widgets skill ready

If Verification Fails

1. Check: references/ folder has widget-patterns.md 2. Stop and report if still failing

References

  • references/widget-patterns.md - Complete widget patterns
  • references/server-action-handler.md - Backend action handling

Related skills

FAQ

What is the difference between client and server actions?

Client actions are handled by the frontend onAction callback for navigation or local state; server actions are handled by the backend action() method for data mutation or widget replacement.

When should I not use this skill?

Not when building simple text-only chat without interactive elements.

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.