Skip to content

Instantly share code, notes, and snippets.

@manzt
Created May 26, 2025 16:19
# /// script
# requires-python = ">=3.13"
# dependencies = [
# "anywidget==0.9.18",
# "marimo",
# "traitlets==5.14.3",
# ]
# ///
import marimo
__generated_with = "0.13.11"
app = marimo.App(width="medium")
@app.cell
def _(genres):
genres.value
return
@app.cell
def _(Tags):
import marimo as mo
genres = mo.ui.anywidget(
Tags(
[
"Action",
"Comedy",
"Drama",
"Thriller",
"Sci-Fi",
"Animation",
"Documentary",
]
)
)
genres
return (genres,)
@app.cell
def _():
import typing
import anywidget
import traitlets
class Tags(anywidget.AnyWidget):
_esm = """
function render({ model, el }) {
el.classList.add("tags-anywidget");
function renderTags() {
el.replaceChildren();
let container = document.createElement("div");
container.className = "tag-list";
model.get("value").forEach((item, index) => {
let tag = document.createElement("span");
tag.className = "tag";
let label = document.createElement("span");
label.className = "tag-label";
label.textContent = item;
let remove = document.createElement("button");
remove.className = "tag-remove";
remove.setAttribute("aria-label", "Remove tag");
remove.innerHTML =
`<svg viewBox="0 0 14 14"><path d="M4 4l6 6m0-6l-6 6"></path></svg>`;
remove.onclick = () => {
model.set("value", model.get("value").toSpliced(index, 1));
model.save_changes();
};
tag.setAttribute("draggable", "true");
tag.dataset.index = index;
tag.ondragstart = (e) => {
e.dataTransfer.setData("text/plain", index);
e.dataTransfer.effectAllowed = "move";
};
tag.ondragover = (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = "move";
};
tag.ondrop = (e) => {
e.preventDefault();
let fromIndex = Number(e.dataTransfer.getData("text/plain"));
let toIndex = Number(tag.dataset.index);
if (fromIndex === toIndex) return;
let updated = [...model.get("value")];
let [moved] = updated.splice(fromIndex, 1);
updated.splice(toIndex, 0, moved);
model.set("value", updated);
model.save_changes();
};
tag.ondragstart = (e) => {
e.dataTransfer.setData("text/plain", index);
tag.classList.add("dragging");
};
tag.ondragend = () => {
tag.classList.remove("dragging");
};
tag.appendChild(label);
tag.appendChild(remove);
container.appendChild(tag);
});
let input = document.createElement("input");
input.type = "text";
input.className = "tag-input";
input.onkeydown = (e) => {
if (
(e.key === "Enter" ||
(e.key === "Enter" && (e.metaKey || e.ctrlKey))) &&
input.value.trim()
) {
e.preventDefault();
let updated = [...model.get("value"), input.value.trim()];
model.set("value", updated);
model.save_changes();
input.value = "";
input.focus();
}
};
container.appendChild(input);
el.appendChild(container);
}
renderTags();
model.on("change:value", renderTags);
}
export default { render };
"""
_css = """
.tags-anywidget {
font-family: system-ui, sans-serif;
display: block;
max-width: 100%;
border: 0px solid rgb(229, 231, 235);
box-sizing: border-box;
.tag-list {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5em;
}
.tag {
display: inline-flex;
align-items: center;
column-gap: 0.125rem;
border-radius: 0.375rem;
background: oklch(96.7% .003 264.542);
padding-inline: 0.5rem;
padding-block: 0.25rem;
font-size: 0.75rem;
font-weight: 500;
color: oklch(44.6% .03 256.802)
}
.tag-remove {
display: flex;
align-items: center;
justify-content: center;
border-radius: 0.125rem;
width: 0.875rem;
height: 0.875rem;
margin-right: -0.25rem;
position: relative;
color: inherit;
opacity: 1;
cursor: pointer;
border: none;
background: transparent;
padding: 0;
}
.tag-remove:hover {
background-color: rgba(0, 0, 0, 0.1);
}
.tag-remove > svg {
display: block;
vertical-align: middle;
stroke: currentColor;
width: 0.75rem;
height: 0.75rem;
}
.tag-input {
flex: 1;
min-width: 120px;
font-size: 0.875rem;
padding: 0.25em 0.5em;
border: none;
outline: none;
}
.tag[draggable="true"] {
cursor: grab;
}
.tag.dragging {
opacity: 0.5;
}
}
"""
value = traitlets.List(traitlets.Unicode()).tag(sync=True)
def __init__(self, value: typing.Sequence[str]) -> None:
super().__init__(value=list(value))
return (Tags,)
if __name__ == "__main__":
app.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment