lemma-work/lemma-platform/lemma-widget

Claude Code and agent skill by lemma-work/lemma-platform

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Create lightweight inline Lemma widgets for conversations via display_resource(type=\"WIDGET\"): self-contained HTML/CSS/JS for metrics, lists, comparisons, timelines, record details, previews, and charts, optionally powered by live pod data through the browser Lemma SDK. Use an app, not a widget, when the UI needs React, routing, or substantial application state. Not for a ```lemma-widget block written into a doc or page: that needs no skill, and the doc conversation says how.

Install

npx vskill@latest install lemma-work/lemma-platform/lemma-widget

SKILL.md

Lemma Widget

Writing a ```lemma-widget block into a doc or page? Stop here — this skill
is not for that. A doc block is a different contract: plain HTML with its data
baked in, no SDK, no display_resource, no loading states, no height cap. The
conversation attached to the doc states the whole of it; follow that, write the
block with pod_edit_file, and do not render or screenshot it to check.

A widget is the default way to show an answer that is more than short prose. Use display_resource(type="WIDGET") whenever the useful result has structure or visual hierarchy: several values, records, statuses, steps, comparisons, a timeline, a compact table, a preview, or a chart.

A widget is also where an answer continues: it reads live pod data, it can be filtered and opened and sorted in place, and it can offer the person their next question ready to send.

Use plain text only for a single fact, a short explanation, or narration around the widget. If an existing FILE, TABLE, APP, or other pod resource already represents the answer, display that resource directly instead of recreating it as a widget.

Start from the library

When the answer is one real thing someone acts on (an email, an invite, a pull request, a support conversation), do not draw it: show a library widget. They live at /skills/lemma-widget/library/<service>/<name>.html, one per service per object, and their buttons act through the viewer's own connected account:

ServiceWidgets
gmailemail-thread, inbox
outlookemail-thread
google-calendarinvite, find-a-time, meeting-brief
githubpr-review, issue
linearissue
slackthread
intercomconversation
linkedinpost
showdiagram, chart, note, image

The header comment at the top of each file is its contract: the data shape it reads, the connector, and every operation its buttons call. Fetch the real record, shape it to that data, and pass it:

display_resource(type="WIDGET", path="/skills/lemma-widget/library/gmail/email-thread.html",
data={"thread_id": "...", "subject": "...", "messages": [...], "draft": "..."})

A draft reply belongs in data.draft; the person edits it and sends.

show is the exception to fetching a record: its widgets draw what you give them and act on nothing. show/diagram takes Mermaid source, show/chart a Vega-Lite spec with its data inline, show/note markdown and show/image an https url, each with an optional title. Reach for them before writing a widget of your own: you write the diagram, the spec or the note, never the HTML around it.

gmail/inbox is the exception to fetching first: pass only a Gmail search, data={"query": "is:unread in:inbox"}, and it reads the mail itself as the viewer. Fetching the messages to pass them in costs a connector round-trip and the whole inbox as tool arguments, for a list the widget draws anyway. Without data a widget shows its own sample, marked as one, and its buttons do nothing.

To change one, copy it to /widgets/<service>/<name>.html and edit the copy: that folder is the pod's own library, so look there first. Keep the header true when you edit, and keep the lw- classes and lemma.button wiring: they are what make it look like the rest and ask before anything leaves Lemma. A new widget for the library follows the same shape, a .lw-card with a header section, the object, and its actions.

Widget or app?

  • Widget: one compact inline view; plain HTML/CSS/JS; quick to render in

the conversation; little local state.

  • Vite app: React, routing, multiple screens, reusable components, substantial

interaction/state, or a UI people will return to as a product.

React, ReactDOM, Tailwind, and the agent web-component bundle belong in a Vite app — Lemma has full app support for that class of UI. A widget stays lightweight, and can be saved as an HTML app later.

A widget is an interactive display: whatever you want to show the person in a form they can work with. Its buttons can do anything in Lemma — write records, run functions and workflows, put something in the conversation, or execute a connector operation — as the person viewing it. ask_user is still how this run gets an answer it needs, because a widget's clicks arrive after the run is over.

Build one

  1. If the widget uses pod data, inspect the real table and column names first:

``bash lemma tables list lemma tables get <table> # exact columns; pods describe folds them lemma query run "select * from <table> limit 5" ``

  1. The page gives you the design language and the data. A widget is served

inside a page that already has, before your markup runs:

  • every --lemma-widget-* token, light and dark — use var(--lemma-widget-surface, #fff)

and friends; never paste the token preamble, it is already there;

  • window.lemma: lemma.query(sql) → rows (rows.truncated when the cap

cut it), lemma.records(table, options), lemma.compose(text) and lemma.canCompose(), lemma.esc / num / money / pct / date, and lemma.fail(message, where?), which says in place that the widget could not draw and tells whoever showed it (uncaught errors are reported the same way). It loads and authenticates the SDK itself on first use — never write an SDK loader;

  • and the shapes most answers take, drawn to the visual standard below

(hover, a hidden data table, mono figures, hairlines, state colours):

ShapeCall
Key numbers, each with its changelemma.stats(el, [{ label, value, previous?, format?, better? }])
One measure across categorieslemma.bars(el, rows, { label, value, format?, top? })
One measure over time, or a few on one chartlemma.line(el, rows, { x, y, format?, xFormat?, names? }) — y a key or several
Two measures against each otherlemma.scatter(el, rows, { x, y, color?, label?, xFormat?, yFormat? })
Records to read down, and narrowlemma.table(el, rows, { columns: [{ key, label, format? }], filter? })
One recordlemma.record(el, row, { fields: [{ key, label, format? }] })

el is an element or its id; format is num, money, pct or date; better: "down" marks a measure where falling is good news (failure rate, cost), so its change wears the right colour.

So a widget is a finding line, one lemma.query and one draw call — about a kilobyte. assets/widget-quick-v1.html is exactly that: start from it. Write the finding as the sentence you would say out loud; keep the error branch. A widget of its own design is still fine when none of these fit — draw it in SVG on the same tokens, or with a library from cdn.jsdelivr.net (a map is d3 + topojson-client + world-atlas; never inline a map or a data file). Keep it to the finding, one query and the drawing.

  1. Only when none of the shapes fits and you are designing your own — a

record's timeline, a finding with its own layout — read the nearest fuller example with load_skill (name="lemma-widget", resource_path below) and take the pattern. They are written on the page kit too: no preamble, no loader.

The answer you haveExample
A judgement, with a distribution behind itassets/widget-finding-v1.html
Records you want read down, and narrowedassets/widget-table-v1.html
One record, and what happened to itassets/widget-record-v1.html
One measure over timeassets/widget-trend-v1.html
One measure across categoriesassets/widget-ranked-v1.html
Something you already know — no pod dataassets/widget-note-v1.html

Their uppercase __PLACEHOLDER__ slots are yours to fill if you borrow one; none may survive into what you display. The finding line is yours, not the data's: a widget that says "22 tickets" where it could say "22 tickets are blocked — the most since June" has made the reader do the work they asked you to do.

  1. Write the fragment once with pod_write_file — /me/c/<date>/<name>.html —

and display that path:

`` display_resource(type="WIDGET", path="/me/c/2026-09-15/pulse.html") ``

Compose it straight into that call. Do not draft it in the workspace, run it through a parser, or render it to check it: the backend validates what you display and says exactly what is wrong if anything is.

The backend rejects unresolved placeholders, incomplete hand-written SDK loaders, and malformed markup before display.

The display_resource call

type="WIDGET" takes exactly one of:

  • path — a pod file holding the widget's HTML. The one to reach for.
  • content — the HTML inline, for something small you will not revisit.
  • public_url — a URL to embed instead.

Passing more than one, or none, is rejected. Three more fields, the first two WIDGET-only (setting them on any other resource type is rejected):

  • data — JSON the widget reads as lemma.data, with path or content.

This is how a library widget gets its record (above).

  • loading_messages — up to 4 short lines shown while the widget renders.
  • fallback — one or two plain-text lines carrying the names and numbers that

matter, for chat apps that cannot draw a widget (Slack, WhatsApp, Teams, email). Write one for every widget.

name, filters, and query belong to other types.

Put it in a file

A widget's HTML is a file you write, the way everything else you build is. That is not a storage detail — it is the difference between work you can go back to and work you get one shot at:

  • You can read back what you wrote instead of trusting that a few thousand

characters came out of a single tool argument intact. One missing < has shipped a widget with a stray bracket over the top of it.

  • You can fix it by editing it. The served widget is whatever the file says

now, so correcting one is an edit to a few lines, not a retype of the whole fragment — and a retype is where a working widget picks up a new bug.

  • It is checked before anyone sees it. display_resource validates the

file and says what is wrong; fix it with an edit and display again. Nothing broken reaches the person, so there is no need to read it back first.

So: pod_write_file to /me/c/<date>/<name>.html, then display_resource(type="WIDGET", path=...). A pod path, never a workspace one — the widget is read as the person looking at it, and they are not in your sandbox.

content still takes an inline fragment, for something small you are sure of. It is frozen in the tool call the moment it succeeds: no editing it, no taking it back, and a second call is a second widget that lands underneath the first with the mistake still showing above it. If you are inlining, you get one look.

Either way:

  • Never put a sentence in the HTML's first line. Not narration, not "clean

version below", not a note about the markup. Content with no tag is rejected, and anything before the first tag renders as a bare unstyled line above the view. What the person should read goes in your reply.

  • Never display a probe. A call that succeeds is shown. "Testing whether

this transmits" is a test run in front of the person — and with a file there is finally somewhere to test that is not their screen.

  • A widget people will come back to is an app. Save the HTML as an app and

pass its address as public_url.

The examples are a shape to follow, not a file to transcribe. Take the loading/empty/error scaffolding, and write the markup your answer actually needs around it.

Fixed contract

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 95 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
5

Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/lemma-work/lemma-platform/lemma-widget/badge)](https://verified-skill.com/skills/lemma-work/lemma-platform/lemma-widget)

Something wrong with this skill or its page? Tell us.