Craft garnish
Garnish — Craft CMS's built-in JavaScript UI toolkit for the control panel. ALWAYS load when writing, editing, or reviewing JavaScript that runs in the Craft CP — plugin CP assets, custom field type JS, element index JS, CP webpack config, or code importing garnishjs / referencing window.Garnish. Also for CP accessibility, keyboard interactions, drag-sort, and CP JS memory issues. Triggers on: Garnish.Base.extend, this.base(), init, setSettings, addListener, on/off/trigger, destroy, garnishjs, GarnishAsset, CpAsset, webpack externals, CP JavaScript, Craft.* pattern, Garnish.Modal, HUD, DisclosureMenu, MenuBtn, CustomSelect, ContextMenu, Select, modal/HUD popover, BaseDrag, DragSort, DragDrop, DragMove, onSortChange/onOptionSelect/onSelectionChange, NiceText, CheckboxSelect, MixedInput, MultiFunctionBtn, ESC_KEY/RETURN_KEY, activate/textchange events, UiLayerManager, registerShortcut, trapFocusWithin/releaseFocusWithin/setFocusWithin, ARIA helpers, focus management, aria-modal, aria-hidden retained focus, inert attribute, focus trap, keyboard navigation CP, Craft.CP, Craft.Slideout, Craft.ElementEditor, Craft.sendActionRequest, CP memory leak, event listener cleanup, jQuery .on() in CP, custom slideout/overlay lifecycle, disable kills listeners, sidebar panel injected into the entry form, id-only inputs with no name attribute, host form swallows my fields, Enter key submits the entry form, lightswitch value in JS. Do NOT trigger for front-end JavaScript (Alpine, Vue, htmx) or Twig templates (craft-site).From its SKILL.md
npx -y skills add michtio/craftcms-claude-skills --skill craft-garnishAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
SKILL.md
7.8 KB, as published. Nobody here has run it
Garnish — Craft CMS Control Panel JavaScript Toolkit
Reference for Garnish, Craft CMS's built-in JavaScript UI framework. Covers the class system, UI widgets, drag interactions, form components, accessibility helpers, and integration with Craft's CP.
This skill is scoped to Garnish itself — the JavaScript library at src/web/assets/garnish/. For PHP-side plugin development (elements, controllers, services), see the craftcms skill. For CP template markup that Garnish widgets attach to, see the craftcms skill's cp.md reference.
Companion Skills — Load When Needed
craftcms— Load when the task involves PHP asset bundle classes, plugin architecture, or CP template markup that Garnish widgets attach to. Skip for pure JavaScript refactoring, Garnish API questions, or JS-only tasks.craft-php-guidelines— Load only when editing PHP files (asset bundle classes, controllers that register JS). Skip for pure JS work.
Documentation
- Garnish source:
src/web/assets/garnish/src/in the Craft CMS repository - No official external documentation exists — this skill IS the documentation.
Use WebFetch on Craft's class reference (https://docs.craftcms.com/api/v5/) when looking up PHP-side asset bundle registration.
Common Pitfalls (Cross-Cutting)
- Using jQuery
.on()directly instead ofthis.addListener()— listeners added via jQuery won't auto-clean ondestroy(), causing memory leaks. - Forgetting
this.base()when overridingdestroy()— parent cleanup (listener removal, event teardown) gets skipped. - Using
clickinstead ofactivateevent on non-<button>elements —activatehandles both click and keyboard (Space/Enter), making the UI accessible. - Fighting
UiLayerManagerby binding ESC directly — useGarnish.uiLayerManager.registerShortcut(Garnish.ESC_KEY, callback)so escape routes through the layer stack correctly. - Magic key code numbers instead of
Garnish.ESC_KEY,Garnish.RETURN_KEY, etc. — constants are self-documenting and consistent. - Instantiating Garnish widgets before the DOM is ready — Garnish requires jQuery and all dependencies loaded first; in plugin assets, rely on
CpAssetdependency chain. - Not calling
destroy()when removing widgets — orphaned listeners accumulate, especially in slideouts and live preview where DOM is repeatedly created/destroyed. - Importing Garnish into webpack bundles instead of using the external —
import Garnish from 'garnishjs'resolves towindow.Garnishvia webpack externals; bundling it duplicates 134KB. - Giving
nameattributes to inputs in a panel injected into another form (an entry-edit sidebar panel, a CP template hook) — the host form serializes them on save and your plugin's values are silently swallowed. Inputs are id-only, your JS reads them by id and posts to your own endpoint, and an Enter-key guard keeps the host form from submitting. Seeintegration.md(Panels Injected Into Another Form). - Using deprecated
Garnish.Menuinstead ofGarnish.CustomSelect—Menuis an alias kept for BC only. - Using deprecated
Garnish.escManagerorGarnish.shortcutManagerinstead ofGarnish.uiLayerManager— the newer manager provides layer-aware keyboard routing that respects the modal/menu stack.
Reference Files
Read the relevant reference file(s) for your task. Multiple files often apply together.
Task examples:
- "Create a modal dialog in a plugin's CP JS" → read
class-system.md+ui-widgets.md - "Add drag-to-reorder to a custom field type" → read
drag-system.md+class-system.md - "Build a custom CP widget class" → read
class-system.md+integration.md - "Add a disclosure menu to a CP template" → read
ui-widgets.md+integration.md - "Handle keyboard events in CP JavaScript" → read
utilities.md+class-system.md - "Create an inline editor HUD" → read
ui-widgets.md(HUD section) - "Make a selection interface for elements" → read
ui-widgets.md(Select section) - "Set up a plugin's webpack config for Garnish" → read
integration.md - "Custom element index class isn't loading" → read
integration.md(Element Index JS Loading) - "Load element index JS with Vite" → read
integration.md(Element Index JS Loading — Vite doesn't work for element index classes) - "Add ARIA attributes to a custom modal" → read
utilities.md(ARIA & Focus section) - "Build a custom slideout/overlay/launcher on Garnish.Base" → read
ui-widgets.md(Building a custom overlay/slideout) +class-system.md(Enable / Disable) +utilities.md(ARIA & Focus) - "My launcher button only works once / 'Blocked aria-hidden' warning" → read
class-system.md(Enable / Disable) +utilities.md(Closing an overlay: move focus OUT before hiding) - "Understand how Craft.CP extends Garnish" → read
integration.md+class-system.md - "Add an interactive panel to an entry edit screen's sidebar from a plugin" → read
integration.md(Panels Injected Into Another Form) + thecraftcmsskill'scp-ui-patterns.mdfor the markup - "My panel's fields disappear when the entry is saved / Enter submits the entry form" → read
integration.md(Panels Injected Into Another Form) - "Build a multi-state submit button" → read
integration.md(Form Widgets section) - "Add auto-growing textarea behavior" → read
integration.md(Form Widgets section)
| Reference | Scope |
|---|---|
references/class-system.md | Garnish.Base, inheritance (extend/init/base), events (on/off/trigger), listeners (addListener/removeListener), settings, namespacing, enable/disable, destroy lifecycle |
references/ui-widgets.md | Modal, HUD, DisclosureMenu, MenuBtn, SelectMenu, CustomSelect, ContextMenu, Select — constructor args, settings/defaults, methods, events, ARIA behavior |
references/drag-system.md | BaseDrag, Drag, DragSort, DragDrop, DragMove — class hierarchy, settings/defaults, events, helper system, insertion points, scroll handling |
references/utilities.md | Garnish namespace object, key constants, custom jQuery events (activate, textchange, resize), ARIA/focus management, geometry/hit testing, animation, form helpers, detection |
references/integration.md | GarnishAsset PHP bundle, webpack externals, loading sequence, Craft.* class pattern, Twig JS blocks, form widgets (NiceText, CheckboxSelect, MultiFunctionBtn, MixedInput) |
What ships with it: 5 files
83.8 KB alongside SKILL.md
references/
- class-system.md11.4 KB
- drag-system.md11.0 KB
- integration.md24.4 KB
- ui-widgets.md20.5 KB
- utilities.md16.5 KB
Gives 0 of the 12 instructions most project setup skills give
Counted across 1,553 of the 3,091 authors here whose files we hold, read 2026-09-06
- Write the configuration filein 36 of 1553
- Create the directory structurein 35 of 1553, across 33 files
- Verify the setupin 31 of 1553, across 28 files
- Run the setup scriptin 30 of 1553, across 29 files
- Pre-determine the required sample sizein 29 of 1553, across 12 files
- Check if the configuration already existsin 29 of 1553
- Document every testin 26 of 1553, across 10 files
- Start with a hypothesisin 26 of 1553, across 11 files
- Ask one question at a timein 22 of 1553
- Test a single variable per testin 21 of 1553, across 9 files
- Read product marketing context before asking questionsin 19 of 1553, across 8 files
- Do not peek and stop earlyin 18 of 1553, across 7 files
Said here and by no other author read
- Use this.addListener instead of jQuery on
- Call this.base when overriding destroy
- Use activate event on non button elements
- Register escape shortcuts with uiLayerManager
- Use Garnish key constants
- Call destroy when removing widgets
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once.