anywidget-generator by marimo-team/skills
npx skills add https://github.com/marimo-team/skills --skill anywidget-generator在使用 vanilla javascript 编写 anywidget 时,请将其置于 _esm 中,并且不要忘记 _css。CSS 样式在浅色模式和深色模式下都应看起来是定制的。除非明确要求做得更多,否则请保持 CSS 简洁。展示 widget 时,必须通过 widget = mo.ui.anywidget(OriginalAnywidget()) 进行包装。如果需要,你也可以使用 pathlib 将 _esm 和 _css 指向外部文件。如果 widget 包含大量复杂的 JavaScript 或 CSS,这样做是合理的。
class CounterWidget(anywidget.AnyWidget):
_esm = """
// 定义主渲染函数
function render({ model, el }) {
let count = () => model.get("number");
let btn = document.createElement("button");
btn.innerHTML = `count is ${count()}`;
btn.addEventListener("click", () => {
model.set("number", count() + 1);
model.save_changes();
});
model.on("change:number", () => {
btn.innerHTML = `count is ${count()}`;
});
el.appendChild(btn);
}
// 重要!我们必须在此处底部导出!
export default { render };
"""
_css = """button{ font-size: 14px; }"""
number = traitlets.Int(0).tag(sync=True)
widget = mo.ui.anywidget(CounterWidget())
widget
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
.value 是一个字典。print(widget.value["number"])
上面是一个适用于简单计数器 widget 的最小示例。通常,由于需要大量的 JavaScript 和 CSS,widget 可能会变得非常大。除非 widget 极其简单,否则你应该考虑使用 pathlib 将 _esm 和 _css 指向外部文件。
分享 anywidget 时,请保持示例最小化。除非明确说明,否则无需将其与 marimo UI 元素结合使用。
除非另有说明,否则请遵循以下假设:
在 _esm 中使用 vanilla JavaScript:
{ model, el } 为参数的 render 函数model.get() 读取特征值model.set() 和 model.save_changes() 更新特征model.on("change:traitname", callback) 监听变化export default { render }; 进行默认导出anywidget.AnyWidget,因此 widget.observe(handler) 仍然是响应状态变化的标准方式。ValueError/TraitError 来指导你,而不是重复该逻辑。包含 _css 样式:
@media (prefers-color-scheme: dark) { ... }包装 widget 以进行展示:
widget = mo.ui.anywidget(OriginalAnywidget())widget.value 访问值保持示例最小化:
外部文件路径:当使用 pathlib 处理外部 _esm/_css 文件时,请保持路径相对于项目目录,考虑为此使用 Path(__file__)。不要读取项目之外的文件(例如 ~/.ssh、~/.env、/etc/)或将它们的内容嵌入到 widget 输出中。
简单更好。相比于巧妙的抽象,更倾向于明显、直接的代码——项目的新手应该能够从上到下阅读代码并理解它,而无需查找框架魔法或追踪间接引用。
每周安装量
577
代码仓库
GitHub 星标数
77
首次出现
2026年2月10日
安全审计
安装于
claude-code419
opencode394
codex372
github-copilot364
gemini-cli363
amp359
When writing an anywidget use vanilla javascript in _esm and do not forget about _css. The css should look bespoke in light mode and dark mode. Keep the css small unless explicitly asked to go the extra mile. When you display the widget it must be wrapped via widget = mo.ui.anywidget(OriginalAnywidget()). You can also point _esm and _css to external files if needed using pathlib. This makes sense if the widget does a lot of elaborate JavaScript or CSS.
class CounterWidget(anywidget.AnyWidget): _esm = """ // Define the main render function function render({ model, el }) { let count = () => model.get("number"); let btn = document.createElement("b8utton"); btn.innerHTML = count is ${count()}; btn.addEventListener("click", () => { model.set("number", count() + 1); model.save_changes(); }); model.on("change:number", () => { btn.innerHTML = count is ${count()}; }); el.appendChild(btn); } // Important! We must export at the bottom here! export default { render }; """ _css = """button{ font-size: 14px; }""" number = traitlets.Int(0).tag(sync=True)
widget = mo.ui.anywidget(CounterWidget()) widget
.value is a dictionary.print(widget.value["number"])
The above is a minimal example that could work for a simple counter widget. In general the widget can become much larger because of all the JavaScript and CSS required. Unless the widget is dead simple, you should consider using external files for _esm and _css using pathlib.
When sharing the anywidget, keep the example minimal. No need to combine it with marimo ui elements unless explicitly stated to do so.
Unless specifically told otherwise, assume the following:
Use vanilla JavaScript in_esm:
render function that takes { model, el } as parametersmodel.get() to read trait valuesmodel.set() and model.save_changes() to update traitsmodel.on("change:traitname", callback)export default { render }; at the bottomanywidget.AnyWidget, so remains the standard way to react to state changes.Dumber is better. Prefer obvious, direct code over clever abstractions—someone new to the project should be able to read the code top-to-bottom and grok it without needing to look up framework magic or trace through indirection.
Weekly Installs
577
Repository
GitHub Stars
77
First Seen
Feb 10, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
claude-code419
opencode394
codex372
github-copilot364
gemini-cli363
amp359
Vue 3 调试指南:解决响应式、计算属性与监听器常见错误
9,900 周安装
widget.observe(handler)ValueError/TraitError guide you instead of duplicating the logic.Include_css styling:
@media (prefers-color-scheme: dark) { ... }Wrap the widget for display :
widget = mo.ui.anywidget(OriginalAnywidget())widget.value which returns a dictionaryKeep examples minimal :
External file paths : When using pathlib for external _esm/_css files, keep paths relative to the project directory, consider using Path(__file__) for this. Do not read files outside the project (e.g., ~/.ssh, ~/.env, /etc/) or embed their contents in widget output.