Agent skill

Create Modal

by marcus in marcus/sidecar

Create declarative modals using the modal library API. An agent skill from marcus/sidecar.

MITAuto-check passed

Install Create Modal

skills CLI
$ npx skills add marcus/sidecar --skill create-modal -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install marcus/sidecar create-modal --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/marcus/sidecar.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/create-modal .claude/skills/create-modal && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
create-modal
GitHub stars
1.1k
Token cost
~2.6k tokens
SKILL.md length
702 words
Files
2 (incl. references)
Skills in repo
19
Repo updated
First seen
Licence
MIT

At a glance

Create declarative modals using the modal library API. An agent skill from marcus/sidecar.

  • Dialogs to the application
  • SKILL.md covers Quick Start, Critical: Modal Initialization…, Constructor and Options and Built-in Sections, plus 5 more sections
  • Instructions only: no scripts, shell commands, URLs or credentials in SKILL.md

What it does

Create Modal is an agent skill from marcus/sidecar. Create declarative modals using the modal library API. Covers modal types (confirm, input, select, form), sections (Text, Buttons, Input, Textarea, Checkbox, Select, List, Combo, When, Custom), rendering with OverlayModal, and keyboard/mouse handling. Use when adding modals or dialogs to the application.

Its SKILL.md is about 2.6k tokens, which your agent loads only when the skill is triggered. The skill folder holds 2 other files, including reference files (for example `references/complete-example.md`).

The repository describes itself as: Use sidecar next to CLI agents for diffs, file trees, conversation history, and task management with td. The licence is MIT.

When your agent uses it

  • Dialogs to the application

Example prompts

  • “/create-modal”

What it can do on your machine

Read from SKILL.md and the folder at commit 3792a4e. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    No scripts in the folder and no shell commands in SKILL.md (its code samples are go).

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Create Modal loads about 2.6k tokens when it runs, and up to ~3.2k if it reads all its reference files. Until then it costs about 80 tokens; SKILL.md has 702 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~80
When it runs · the whole SKILL.md, loaded when a task matches
~2.6k
With references · SKILL.md plus every file in references/, read only if the agent opens them
~3.2k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from marcus/sidecar at commit 3792a4e, republished under its MIT licence (© marcus). 702 words, ~2,643 tokens.

Download SKILL.mdSave it as .claude/skills/create-modal/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub.
name
create-modal
description
Create declarative modals using the modal library API. Covers modal types (confirm, input, select, form), sections (Text, Buttons, Input, Textarea, Checkbox, Select, List, Combo, When, Custom), rendering with OverlayModal, and keyboard/mouse handling. Use when adding modals or dialogs to the application.

Creating Declarative Modals

Use the internal/modal package. The library handles keyboard navigation, mouse hit regions, hover states, and scrolling automatically.

Quick Start

go
import "github.com/marcus/sidecar/internal/modal"

// 1. Create the modal
m := modal.New("Delete Worktree?",
    modal.WithWidth(58),
    modal.WithVariant(modal.VariantDanger),
    modal.WithPrimaryAction("delete"),
).
    AddSection(modal.Text("Name: " + wt.Name)).
    AddSection(modal.Spacer()).
    AddSection(modal.Buttons(
        modal.Btn(" Delete ", "delete", modal.BtnDanger()),
        modal.Btn(" Cancel ", "cancel"),
    ))

// 2. Render in View
func (p *Plugin) View(width, height int) string {
    background := p.renderListView(width, height)
    rendered := p.myModal.Render(width, height, p.mouseHandler)
    return ui.OverlayModal(background, rendered, width, height)
}

// 3. Handle input in Update
case tea.KeyMsg:
    action, cmd := p.myModal.HandleKey(msg)
    if action != "" {
        return p.handleAction(action) // "delete", "cancel", etc.
    }
    return p, cmd

case tea.MouseMsg:
    action := p.myModal.HandleMouse(msg, p.mouseHandler)
    if action != "" {
        return p.handleAction(action)
    }
    return p, nil

Critical: Modal Initialization Pattern

The modal must exist before input handling. Create an ensure function called in both View and Update:

go
func (p *Plugin) ensureMyModal() {
    if p.targetItem == nil {
        return // Required state missing
    }

    modalW := 50
    if modalW > p.width-4 {
        modalW = p.width - 4
    }
    if modalW < 20 {
        modalW = 20
    }

    // Only rebuild if needed
    if p.myModal != nil && p.myModalWidthCache == modalW {
        return
    }
    p.myModalWidthCache = modalW

    p.myModal = modal.New("Title", modal.WithWidth(modalW), ...).
        AddSection(...)
}

Call ensureModal() before the nil check in key handlers:

go
func (p *Plugin) handleMyModalKeys(msg tea.KeyMsg) tea.Cmd {
    p.ensureMyModal()  // CRITICAL: Before nil check
    if p.myModal == nil {
        return nil
    }
    action, cmd := p.myModal.HandleKey(msg)
    // ...
}

Without this, the first keypress after opening drops because View runs after Update in bubbletea.

Constructor and Options

go
m := modal.New(title string, opts ...Option)
OptionDescriptionDefault
WithWidth(int)Modal width in characters50
WithVariant(Variant)Visual styleVariantDefault
WithPrimaryAction(string)Action ID for Enter on inputs""
WithHints(bool)Show "Tab to switch..." hinttrue
WithCloseOnBackdropClick(bool)Backdrop click returns "cancel"true

Variants: VariantDefault, VariantDanger (red), VariantWarning (yellow), VariantInfo (blue)

Built-in Sections

Text and Spacer
go
modal.Text("Static text with auto line wrapping")
modal.Spacer()  // Single blank line
Buttons
go
modal.Buttons(
    modal.Btn(" Save ", "save"),              // Standard button
    modal.Btn(" Delete ", "delete", modal.BtnDanger()),  // Red
    modal.Btn(" Submit ", "submit", modal.BtnPrimary()), // Primary
    modal.Btn(" Cancel ", "cancel"),
)
  • Include padding in labels: " Save " not "Save"
  • Button IDs are returned as actions
  • Tab/Shift+Tab cycles focus
Input
go
var nameInput textinput.Model
modal.Input("name-input", &nameInput)
modal.InputWithLabel("name-input", "Name:", &nameInput)
modal.Input("name-input", &nameInput,
    modal.WithSubmitOnEnter(true),       // Default: true
    modal.WithSubmitAction("submit"),    // Override primary action
)
Textarea
go
var msgArea textarea.Model
modal.Textarea("message", &msgArea, 5)          // height in lines
modal.TextareaWithLabel("message", "Label:", &msgArea, 5)
  • Enter inserts newlines (never submits)
Combo (floating dropdown)
go
items := []modal.DropdownItem{
    {ID: "main", Label: "main", Value: "main"},
    {ID: "dev", Label: "dev", Value: "dev"},
}
var selectedIdx int
modal.Combo("branch", &branchInput, items, &selectedIdx)
  • Single-line input; filtered results float over later sections (modal height does not change)
  • selected is an items index (same as List)
  • Typing filters and selects the top match; up/down move the highlight
  • Enter commits the highlight and, by default, returns the modal primary action
  • Tab commits and moves focus; Esc closes the overlay without cancelling the modal
  • Click an overlay row to commit without submitting
Checkbox
go
var includeFiles bool
modal.Checkbox("include-files", "Include untracked files", &includeFiles)
  • Space toggles
  • Enter does not toggle; it submits the modal primary action (if any)
Select (one choice out of a set)
go
items := []modal.SelectItem{
    {ID: "shell", Label: "Shell", Description: "new agent/shell session"},
    {ID: "worktree", Label: "Worktree", Description: "shell in a new worktree"},
}
var selectedIdx int
modal.Select("kind", items, &selectedIdx,
    modal.WithMaxVisible(6),
    modal.WithDisabled(func(i int) string { return reasons[i] }),
    modal.WithOnSelect(func(i int) { rebuildAround(i) }),
)
  • The default control for a single choice: sort, filter, kind. modal.List is the low-level column of rows for lists that are not a single choice.
  • Two shapes, chosen by count: a segmented [ A | B | C ] under five choices, a ❯-cursor full-width list with an aligned description column at five or more (and the list whenever the segments would not fit the width). WithShape(modal.ShapeList) / WithShape(modal.ShapeSegmented) forces one.
  • The list shape is a bordered control: its rows and scroll markers sit inside a rounded border that follows focus exactly as modal.Input's does (BorderNormal idle, Primary focused, TextMuted hovered). A click on a border cell focuses the control and selects nothing. The selected row keeps its Primary fill either way — the fill says which choice is active, the border says where the keyboard is.
  • Size the modal to it. A segmented control cannot shrink: build the sections into a slice first and pass modal.WidthForSections(sections...), capped to your frame, to modal.WithWidth — otherwise a control too wide for the box truncates into a stub. See viewModalWidth in internal/pluginbrowser/overlays.go.
  • Arrows and h/j/k/l move by one and stop at the ends; home/end jump; Enter activates.
  • WithDisabled(func(i int) string) keeps a choice visible and muted with its reason in place of its description, and makes it unreachable by key or click.
  • WithMaxVisible(n) scrolls the rest, with ↑ more above / ↓ more below.
  • A click resolves to a row inside the section — hosts add no glue — and focuses the control.
  • WithOnSelect(func(i int)) reports every change; WithSelectAction(id) makes activation return a fixed action instead of the row's ID, for a selector embedded in a form.
  • See docs/reference/design-language.md ("Selectors").
Show full SKILL.md (206 more words)Show less
List
go
items := []modal.ListItem{
    {ID: "item-1", Label: "First item", Data: someValue},
    {ID: "item-2", Label: "Second item"},
}
var selectedIdx int
modal.List("my-list", items, &selectedIdx, modal.WithMaxVisible(5))
  • j/k or up/down moves selection; Enter returns selected item's ID
When (Conditional)
go
modal.When(func() bool { return showWarning },
    modal.Text("Warning: This action is irreversible!"),
)
Custom
go
modal.Custom(
    func(contentWidth int, focusID, hoverID string) modal.RenderedSection {
        return modal.RenderedSection{
            Content: content,
            Focusables: []modal.FocusableInfo{
                {ID: "custom-btn", OffsetX: 0, OffsetY: 2, Width: 10, Height: 1},
            },
        }
    },
    func(msg tea.Msg, focusID string) (string, tea.Cmd) {
        return "", nil  // can be nil if no custom input handling
    },
)

Handling Input

Keyboard
go
action, cmd := m.HandleKey(msg)
KeyBehavior
TabFocus next element
Shift+TabFocus previous element
EnterReturn focused element's ID (or primaryAction for inputs). Checkbox Enter submits primary without toggling. Combo Enter commits then submits.
EscOffered to the focused section first (Combo closes its overlay). Otherwise "cancel".
OtherForwarded to focused section
Mouse
go
action := m.HandleMouse(msg, p.mouseHandler)
EventBehavior
Click backdropReturn "cancel" (if enabled)
Click button/checkboxReturn element ID
Hover elementUpdate hover state
Scroll on modalScroll content

Modal Methods

go
m.FocusedID() string   // Currently focused element ID
m.HoveredID() string   // Currently hovered element ID
m.SetFocus(id string)  // Focus specific element
m.Reset()              // Reset focus, hover, scroll to initial state

Rendering Rules

Always use ui.OverlayModal for dimmed background:

go
func (p *Plugin) View(width, height int) string {
    background := p.renderNormalView(width, height)
    rendered := p.myModal.Render(width, height, p.mouseHandler)
    return ui.OverlayModal(background, rendered, width, height)
}

Do not:

  • Pre-center modal content with lipgloss.Place (OverlayModal handles centering)
  • Render footers or hint lines in plugin View (app renders unified footer)

State Management

  • Focus state persists across renders
  • Call Reset() when closing and reopening modals
  • Width caching should include state-dependent changes

Troubleshooting

IssueSolution
First keypress droppedCall ensureModal() before nil check in Update
Modal too wide/narrowUse width clamping: modalW > p.width-4
Hover not updatingPass mouseHandler to both Render and HandleMouse
Input not receiving keysCheck FocusedID()
Modal rebuilds every frameCache by width
Modal shows with wrong focusCall m.Reset() when showing modal

See references/complete-example.md for a full plugin implementation with delete confirmation modal.

© marcus, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 1 other file (references) in .claude/skills/create-modal of marcus/sidecar.

  • SKILL.md
  • references/complete-example.md

Open the folder on GitHubat commit 3792a4e

Compare with similar skills

Create Modal next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Create Modal compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Create Modal this skillmarcus/sidecar1.1k—~2.6kAutomated safety check: PassMIT
Type Declarationsnubjs/nub4.4k—~2.4kAutomated safety check: PassMIT
Imperative Modal APIlobehub/lobehub83k—~1.3kAutomated safety check: PassCustom licence
ModalK-Dense-AI/scientific-agent-skills48k1 repos~4.5kAutomated safety check: NotesApache-2.0
Rust Path Typesopeninterpreter/openinterpreter69k2 repos~605Automated safety check: PassApache-2.0
Python Type Safetywshobson/agents40k—~1.4kAutomated safety check: PassMIT

Similar skills

  • Add, audit, or update @nubjs/types declarations for Nub runtime APIs.

    4.4k GitHub stars~2.4k tokensUpdated today
    Auto-check passed
  • Imperative Modal API

    lobehub/lobehub

    Shows how to build modals, dialogs and confirmations in LobeHub with the base-ui imperative API (createModal, confirmModal, ModalHost) instead of declarative Modal components.

    83k GitHub stars~1.3k tokensUpdated today
    Frontend & DesignAuto-check passed
  • Modal

    K-Dense-AI/scientific-agent-skills

    Modal is a serverless cloud platform for running Python on demand, including on-demand GPUs.

    48k GitHub starsUsed in 1 repo~4.5k tokens
    Backend & APIsAuto-check: notes
  • Rust Path Types

    openinterpreter/openinterpreter

    Rules for choosing Rust types for filesystem paths in new Codex code, covering protocol types, internal use and model tool arguments.

    69k GitHub starsUsed in 2 repos~605 tokens
    DevelopmentAuto-check passed
  • Python Type Safety

    wshobson/agents

    Python type safety with type hints, generics, protocols, and strict type checking.

    40k GitHub stars~1.4k tokensUpdated 3 days ago
    DevelopmentAuto-check passed
  • Modal

    davila7/claude-code-templates

    Run Python code in the cloud with serverless containers, GPUs, and autoscaling.

    32k GitHub starsUsed in 8 repos~2.6k tokens
    Backend & APIsAuto-check passed

More from marcus/sidecar

All 19 skills in this repo
  • Create Adapter

    marcus/sidecar

    Create conversation adapters for importing AI chat history from different tools (Claude Code, Cursor, Warp, Codex, etc.).

    1.1k GitHub stars~4.8k tokensUpdated 2 days ago
    Auto-check passed
  • Create Plugin

    marcus/sidecar

    Create new sidecar plugins implementing the plugin.Plugin interface, rendering views with Bubble Tea, handling keyboard input via keymap contexts, and integrating with the app shell (footer hints…

    1.1k GitHub stars~2.3k tokensUpdated 2 days ago
    Auto-check passed
  • Project Switching

    marcus/sidecar

    Project switching implementation in sidecar: project discovery, state management, UI flow, modal rendering, filtering, theme preview, and plugin reinitialization.

    1.1k GitHub stars~1.8k tokensUpdated 2 days ago
    Auto-check passed
  • Shell Integration

    marcus/sidecar

    Interactive shell/TTY integration with tmux session management, shell command execution, control-mode output capture with polling fallback, native cursor rendering, lazy scrollback, selection, paste…

    1.1k GitHub stars~3.6k tokensUpdated 2 days ago
    Auto-check passed
  • Sidecar Website

    marcus/sidecar

    Writing and maintaining the Sidecar Docusaurus documentation site, including page structure, doc authoring, blog posts, styling, images, and deployment workflow.

    1.1k GitHub stars~1.7k tokensUpdated 2 days ago
    Auto-check passed
  • UI Features

    marcus/sidecar

    Implementing UI/UX features in sidecar including modals (internal/modal library), keyboard shortcuts, mouse support, scrolling, pill/tab rendering, and pane resizing.

    1.1k GitHub stars~4.6k tokensUpdated 2 days ago
    Auto-check passed

Questions about Create Modal

What does Create Modal do?

Create declarative modals using the modal library API. An agent skill from marcus/sidecar. Create Modal is an agent skill from marcus/sidecar. Create declarative modals using the modal library API.

When should I use Create Modal?

Create Modal fits situations like: dialogs to the application.

How do I install Create Modal in Claude Code?

Run `npx skills add marcus/sidecar --skill create-modal -a claude-code`. Or copy the skill folder (.claude/skills/create-modal in marcus/sidecar) into .claude/skills/create-modal in your project. Claude Code loads it when a task matches its description.

How do I install Create Modal in Codex?

Run `npx skills add marcus/sidecar --skill create-modal -a codex`. Or copy the skill folder (.claude/skills/create-modal in marcus/sidecar) into .agents/skills/create-modal in your project. Codex loads it when a task matches its description.

Can I use Create Modal in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add marcus/sidecar --skill create-modal -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/create-modal, .gemini/skills/create-modal, .github/skills/create-modal and .opencode/skills/create-modal in your project.

What does Create Modal need to run?

SKILL.md names no scripts, command-line tools or credentials: Create Modal is instructions for the agent only.

Does Create Modal access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Create Modal safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Create Modal use?

Create Modal is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Create Modal use?

About 2.6k tokens (SKILL.md is roughly 11k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 558 tokens, read only when the agent opens those files.

What are the alternatives to Create Modal?

Skills that share tags, products or a category with Create Modal: Type Declarations (nubjs/nub, 4.4k stars), Imperative Modal API (lobehub/lobehub, 83k stars), Modal (K-Dense-AI/scientific-agent-skills, 48k stars) and Rust Path Types (openinterpreter/openinterpreter, 69k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Create Modal?

marcus (a GitHub user) maintains it in marcus/sidecar, which has 1,085 GitHub stars. The repository holds 19 skills in this directory. The repository was last updated on October 5, 2026.

Source: marcus/sidecar on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.