lemma-work/lemma-platform/lemma-widget
Claude Code and agent skill by lemma-work/lemma-platform
- community
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-widgetSKILL.md
Lemma Widget
Writing a ```lemma-widget block into a doc or page? Stop here — this skillis not for that. A doc block is a different contract: plain HTML with its databaked in, no SDK, nodisplay_resource, no loading states, no height cap. Theconversation attached to the doc states the whole of it; follow that, write theblock withpod_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:
| Service | Widgets | |
|---|---|---|
| gmail | email-thread, inbox | |
| outlook | email-thread | |
| google-calendar | invite, find-a-time, meeting-brief | |
| github | pr-review, issue | |
| linear | issue | |
| slack | thread | |
| intercom | conversation | |
| post | ||
| show | diagram, 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:
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
- 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" ``
- 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 — usevar(--lemma-widget-surface, #fff)
and friends; never paste the token preamble, it is already there;
window.lemma:lemma.query(sql)→ rows (rows.truncatedwhen 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):
| Shape | Call | |
|---|---|---|
| Key numbers, each with its change | lemma.stats(el, [{ label, value, previous?, format?, better? }]) | |
| One measure across categories | lemma.bars(el, rows, { label, value, format?, top? }) | |
| One measure over time, or a few on one chart | lemma.line(el, rows, { x, y, format?, xFormat?, names? }) — y a key or several | |
| Two measures against each other | lemma.scatter(el, rows, { x, y, color?, label?, xFormat?, yFormat? }) | |
| Records to read down, and narrow | lemma.table(el, rows, { columns: [{ key, label, format? }], filter? }) | |
| One record | lemma.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.
- 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 have | Example | |
|---|---|---|
| A judgement, with a distribution behind it | assets/widget-finding-v1.html | |
| Records you want read down, and narrowed | assets/widget-table-v1.html | |
| One record, and what happened to it | assets/widget-record-v1.html | |
| One measure over time | assets/widget-trend-v1.html | |
| One measure across categories | assets/widget-ranked-v1.html | |
| Something you already know — no pod data | assets/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.
- 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 aslemma.data, withpathorcontent.
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_resourcevalidates 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
- SKILL.md in the repositoryCheckinglemma-skills/lemma-widget/SKILL.md
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
[](https://verified-skill.com/skills/lemma-work/lemma-platform/lemma-widget)