> For the complete documentation index, see [llms.txt](https://ghiscoding.gitbook.io/slickgrid-universal/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ghiscoding.gitbook.io/slickgrid-universal/grid-functionalities/menu-slots.md).

# Custom Menu Slots

All menu plugins (Header Menu, Cell Menu, Context Menu, Grid Menu) support **cross-framework compatible slot rendering** for custom content injection in menu items. This is achieved through the `slotRenderer` callback at the item level combined with an optional `defaultMenuItemRenderer` at the menu level.

> **Note:** This documentation covers **how menu items are rendered** (visual presentation). If you need to **dynamically modify which commands appear** in the menu (filtering, sorting, adding/removing items), see the `commandListBuilder` callback documented in [Grid Menu](/slickgrid-universal/grid-functionalities/grid-menu.md), [Context Menu](/slickgrid-universal/grid-functionalities/context-menu.md), or [Header Menu](/slickgrid-universal/grid-functionalities/header-menu-header-buttons.md).

## TypeScript Tip: Type Inference with commandListBuilder

When using `commandListBuilder` to add custom menu items with slotRenderer callbacks, **cast the return value to the appropriate type** to enable proper type parameters in callbacks:

```typescript
contextMenu: {
  commandListBuilder: (builtInItems) => {
    return [
      ...builtInItems,
      {
        command: 'custom-action',
        title: 'My Action',
        slotRenderer: (cmdItem, args) => `<div>${cmdItem.title}</div>`,
      }
    ] as Array<MenuCommandItem | GridMenuItem | 'divider'>;
  }
}
```

Alternatively, if you only have built-in items and dividers, you can use the simpler cast:

```typescript
return [...] as Array<MenuCommandItem | 'divider'>;
```

## Core Concept

Each menu item can define a `slotRenderer` callback function that receives the item and args, and returns either an HTML string or an HTMLElement. This single API works uniformly across all menu plugins.

## Slot Renderer Callback

```typescript
slotRenderer?: (cmdItem: MenuItem, args: MenuCallbackArgs, event?: Event) => string | HTMLElement
```

* **cmdItem** - The menu cmdItem object containing command, title, iconCssClass, etc.
* **args** - The callback args providing access to grid, column, dataContext, and other context
* **event** - Optional DOM event passed during click handling (allows `stopPropagation()`)

## Basic Example - HTML String Rendering

```typescript
const menuItem = {
  command: 'custom-command',
  title: 'Custom Action',
  iconCssClass: 'mdi mdi-star',
  // Return custom HTML string for the entire menu item
  slotRenderer: () => `
    <div class="custom-menu-item">
      <i class="mdi mdi-star"></i>
      <span>Custom Action</span>
      <span class="badge">NEW</span>
    </div>
  `
};
```

## Advanced Example - HTMLElement Objects

This approach is safer since it's CSP compliant with its use native HTML Elements. This could also be used to add event listeners or simply use the `action` callback.

```typescript
// Create custom element with full DOM control
const menuItem = {
  command: 'notifications',
  title: 'Notifications',
  // Return HTMLElement for more control and event listeners
  slotRenderer: (cmdItem, args) => {
    const container = document.createElement('div');
    container.style.display = 'flex';
    container.style.alignItems = 'center';

    const icon = document.createElement('i');
    icon.className = 'mdi mdi-bell';
    icon.style.marginRight = '8px';

    const text = document.createElement('span');
    text.textContent = cmdItem.title;

    const badge = document.createElement('span');
    badge.className = 'badge';
    badge.textContent = '5';
    badge.style.marginLeft = 'auto';

    container.appendChild(icon);
    container.appendChild(text);
    container.appendChild(badge);

    return container;
  }
};
```

## Default Menu-Level Renderer

Set a `defaultMenuItemRenderer` at the menu option level to apply to all items (unless overridden by individual `slotRenderer`):

```typescript
const menuOption = {
  // Apply this renderer to all menu items (can be overridden per item)
  defaultMenuItemRenderer: (cmdItem, args) => {
    return `
      <div style="display: flex; align-items: center; gap: 8px;">
        ${cmdItem.iconCssClass ? `<i class="${cmdItem.iconCssClass}" style="font-size: 18px;"></i>` : ''}
        <span style="flex: 1;">${cmdItem.title}</span>
      </div>
    `;
  },
  commandItems: [
    {
      command: 'action-1',
      title: 'Action One',
      iconCssClass: 'mdi mdi-check',
      // This item uses defaultMenuItemRenderer
    },
    {
      command: 'custom',
      title: 'Custom Item',
      // This item overrides defaultMenuItemRenderer with its own slotRenderer
      slotRenderer: () => `
        <div style="color: #ff6b6b; font-weight: bold;">
          Custom rendering overrides default
        </div>
      `
    }
  ]
};
```

## Menu Types & Configuration

The `slotRenderer` and `defaultMenuItemRenderer` work identically across all menu plugins:

### Header Menu

```typescript
const columnDef = {
  id: 'name',
  header: {
    menu: {
      defaultMenuItemRenderer: (cmdItem, args) => `<div>${cmdItem.title}</div>`,
      commandItems: [
        {
          command: 'sort',
          title: 'Sort',
          slotRenderer: () => '<div>Custom sort</div>'
        }
      ]
    }
  }
};
```

### Cell Menu

```typescript
const columnDef = {
  id: 'action',
  cellMenu: {
    defaultMenuItemRenderer: (cmdItem, args) => `<div>${cmdItem.title}</div>`,
    commandItems: [
      {
        command: 'edit',
        title: 'Edit',
        slotRenderer: (cmdItem, args) => `<div>Edit row ${args.dataContext.id}</div>`
      }
    ]
  }
};
```

### Context Menu

```typescript
const gridOptions = {
  enableContextMenu: true,
  contextMenu: {
    defaultMenuItemRenderer: (cmdItem, args) => `<div>${cmdItem.title}</div>`,
    commandItems: [
      {
        command: 'export',
        title: 'Export',
        slotRenderer: () => '<div>📊 Export data</div>'
      }
    ]
  }
};
```

### Grid Menu

```typescript
const gridOptions = {
  enableGridMenu: true,
  gridMenu: {
    defaultMenuItemRenderer: (cmdItem, args) => `<div>${cmdItem.title}</div>`,
    commandItems: [
      {
        command: 'refresh',
        title: 'Refresh',
        slotRenderer: () => '<div>🔄 Refresh data</div>'
      }
    ]
  }
};
```

## Real-World Use Cases

### 1. Add Keyboard Shortcuts

```typescript
{
  command: 'copy',
  title: 'Copy',
  iconCssClass: 'mdi mdi-content-copy',
  slotRenderer: () => `
    <div style="display: flex; align-items: center; gap: 8px;">
      <i class="mdi mdi-content-copy" style="font-size: 18px;"></i>
      <span style="flex: 1;">Copy</span>
      <kbd style="background: #eee; border: 1px solid #ccc; border-radius: 2px; padding: 2px 4px; font-size: 10px;">Ctrl+C</kbd>
    </div>
  `
}
```

### 2. Add Status Indicators

```typescript
{
  command: 'filter',
  title: 'Filter',
  iconCssClass: 'mdi mdi-filter',
  slotRenderer: () => `
    <div style="display: flex; align-items: center; gap: 8px;">
      <i class="mdi mdi-filter" style="font-size: 18px;"></i>
      <span style="flex: 1;">Filter</span>
      <span style="width: 6px; height: 6px; border-radius: 50%; background: #44ff44; box-shadow: 0 0 4px #44ff44;"></span>
    </div>
  `
}
```

### 3. Add Dynamic Content Based on Context

```typescript
{
  command: 'edit-row',
  title: 'Edit Row',
  slotRenderer: (cmdItem, args) => `
    <div style="display: flex; align-items: center; gap: 8px;">
      <i class="mdi mdi-pencil" style="font-size: 18px;"></i>
      <span>Edit Row #${args.dataContext?.id || 'N/A'}</span>
    </div>
  `
}
```

### 4. Add Interactive Elements

```typescript
{
  command: 'toggle-setting',
  title: 'Auto Refresh',
  slotRenderer: (cmdItem, args, event) => {
    const container = document.createElement('label');
    container.style.display = 'flex';
    container.style.alignItems = 'center';
    container.style.gap = '8px';
    container.style.marginRight = 'auto';

    const checkbox = document.createElement('input');
    checkbox.type = 'checkbox';
    checkbox.addEventListener('change', (e) => {
      // Prevent menu item click from firing when toggling checkbox
      event?.stopPropagation?.();
      console.log('Auto refresh:', checkbox.checked);
    });

    const label = document.createElement('span');
    label.textContent = cmdItem.title;

    container.appendChild(label);
    container.appendChild(checkbox);
    return container;
  }
}
```

### 5. Add Badges and Status Labels

```typescript
{
  command: 'export-excel',
  title: 'Export as Excel',
  slotRenderer: (cmdItem, args) => `
    <div style="display: flex; align-items: center; gap: 8px;">
      <i class="mdi mdi-file-excel-outline"></i>
      <span style="flex: 1;">${cmdItem.title}</span>
      <span style="background: #44ff44; color: #000; padding: 2px 4px; border-radius: 3px; font-size: 9px; font-weight: bold;">RECOMMENDED</span>
    </div>
  `
}
```

### 6. Gradient and Styled Icons

```typescript
{
  command: 'advanced-export',
  title: 'Advanced Export',
  slotRenderer: (cmdItem, args) => {
    const container = document.createElement('div');
    container.style.display = 'flex';
    container.style.alignItems = 'center';
    container.style.gap = '8px';

    const iconDiv = document.createElement('div');
    iconDiv.style.width = '20px';
    iconDiv.style.height = '20px';
    iconDiv.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
    iconDiv.style.borderRadius = '4px';
    iconDiv.style.display = 'flex';
    iconDiv.style.alignItems = 'center';
    iconDiv.style.justifyContent = 'center';
    iconDiv.style.color = 'white';
    iconDiv.style.fontSize = '12px';
    iconDiv.innerHTML = '📊';

    const textSpan = document.createElement('span');
    textSpan.textContent = cmdItem.title;

    container.appendChild(iconDiv);
    container.appendChild(textSpan);
    return container;
  }
}
```

## Notes and Best Practices

* **HTML strings** are inserted via `innerHTML` - ensure content is sanitized if user-provided
* **HTMLElement objects** are appended directly - safer for dynamic content and allows event listeners
* **Cross-framework compatible** - works in vanilla JS and any other frameworks using the same API
* **Priority order** - Item-level `slotRenderer` overrides menu-level `defaultMenuItemRenderer`
* **Built-in command preservation** - When overriding a built-in command (e.g., `sort-asc`, `sort-desc`, `hide`, etc.) with custom properties like `slotRenderer` or `iconCssClass`, if you don't provide an `action` callback, the library will automatically preserve and use the built-in action for that command. This means you can safely customize the appearance of built-in commands without losing their functionality.
* **Accessibility** - Include proper ARIA attributes when creating custom elements
* **Event handling** - Call `event.stopPropagation()` in interactive elements to prevent menu commands from firing and closing the menu
* **Default fallback** - If neither `slotRenderer` nor `defaultMenuItemRenderer` is provided, the default icon + text rendering is used
* **Performance** - Avoid heavy DOM manipulation inside renderer callbacks (they may be called multiple times)
* **Event parameter** - The optional `event` parameter is passed during click handling and allows you to control menu behavior
* **All menus supported** - This API works uniformly across Header Menu, Cell Menu, Context Menu, and Grid Menu

## Styling Custom Menu Items

```css
/* Example CSS for styled menu items */
.slick-menu-item {
  padding: 4px 8px;
}

.slick-menu-item div {
  display: flex;
  align-items: center;
  gap: 8px;
}

.slick-menu-item kbd {
  background: #f0f0f0;
  border: 1px solid #ddd;
  border-radius: 3px;
  padding: 2px 6px;
  font-size: 11px;
  font-family: monospace;
  color: #666;
}

.slick-menu-item .badge {
  background: #ff6b6b;
  color: white;
  padding: 2px 6px;
  border-radius: 3px;
  font-size: 9px;
  font-weight: bold;
  white-space: nowrap;
}

.slick-menu-item:hover {
  background: #f5f5f5;
}

.slick-menu-item.slick-menu-item-disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
```

## Migration from Static Rendering

**Before (Static HTML Title):**

```typescript
{
  command: 'action',
  title: 'Action ⭐',  // Emoji embedded in title
  iconCssClass: 'mdi mdi-star'
}
```

**After (Custom Rendering):**

```typescript
{
  command: 'action',
  title: 'Action',
  slotRenderer: () => `
    <div style="display: flex; align-items: center; gap: 8px;">
      <i class="mdi mdi-star" style="font-size: 18px;"></i>
      <span style="flex: 1;">Action</span>
      <span style="font-size: 18px;">⭐</span>
    </div>
  `
}
```

## Error Handling

When creating custom renderers, handle potential errors gracefully:

```typescript
{
  command: 'safe-render',
  title: 'Safe Render',
  slotRenderer: (cmdItem, args) => {
    try {
      if (args?.dataContext?.status === 'error') {
        return `<div style="color: red;">❌ Error loading</div>`;
      }
      return `<div>✓ Data loaded</div>`;
    } catch (error) {
      console.error('Render error:', error);
      return '<div>Render error</div>';
    }
  }
}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://ghiscoding.gitbook.io/slickgrid-universal/grid-functionalities/menu-slots.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
