Files
appsmith-statistics-app/.cursor/skills/add-tabs-to-page/SKILL.md
Adam Pitel b5e74cebb7 Add Operations - WO Shortages page with MTS/MTO shortage tracking
Automates the production manager's manual workflow of checking xTuple WO
Schedule + Kit Material Shortage for FA department work orders. Two tabs:
WO Shortages (detail per WO + shortage line with customer name, YYYY-MM-DD
dates) and Critical Parts (aggregated parts blocking near-term shipments
with QOH from MPE warehouse). Nav button added to all pages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:47:58 -04:00

6.7 KiB

name, description
name description
add-tabs-to-page Adds a TABS_WIDGET to a page so multiple data views live under one page instead of separate pages. Use when the user asks to add tabs, consolidate pages into tabs, create tabbed views, or replace a single table with a tabbed layout.

Add Tabs to a Page

Replaces a page's standalone content (e.g. a single TABLE_WIDGET_V2) with a TABS_WIDGET containing multiple tabs, each with its own widgets and query binding.

Prerequisites

  • Tab definitions: User must provide the tab labels and the query each tab should display.
  • Page prefix: Each page uses a short widget-ID prefix (e.g. pa1, cp1). Reuse the existing prefix from the page's widgets.
  • Follow the add-query-to-table skill for creating any new queries needed by the tabs.

File Layout

The Tabs widget and all widgets inside tabs live under a Tabs1/ directory:

pages/<PageName>/widgets/Tabs1/
├── Tabs1.json          # The TABS_WIDGET itself
├── <Table1>.json       # Table inside tab 1
├── <Table2>.json       # Table inside tab 2
├── <DatePicker>.json   # Optional: date pickers inside a tab
└── ...

If the page previously had a standalone Table1.json at widgets/Table1.json, delete it after creating the tabbed replacement.

Instructions

1. Design the tab structure

Decide on tab count, labels, and content. Example:

Tab ID Label Canvas widgetId Content
tab1 All {prefix}cnvsall TableAll bound to query_all
tab2 SLx {prefix}cnvsslx TableSLx bound to query_slx

2. Create Tabs1.json

The TABS_WIDGET has three critical sections:

A) tabsObj — declares each tab with id, label, index, and canvas widgetId:

"tabsObj": {
  "tab1": {
    "id": "tab1",
    "index": 0,
    "isVisible": true,
    "label": "All",
    "positioning": "vertical",
    "widgetId": "<canvas-widgetId-for-tab1>"
  },
  "tab2": { ... }
}

B) children — array of CANVAS_WIDGET entries, one per tab. Each canvas must reference:

  • "parentId": the Tabs widget's widgetId
  • "tabId": matching key from tabsObj (e.g. "tab1")
  • "tabName": the display label
  • "widgetId": unique canvas ID (referenced in tabsObj and by child widgets)

**C) Top-level Tabs properties:

{
  "type": "TABS_WIDGET",
  "version": 3,
  "isCanvas": true,
  "shouldShowTabs": true,
  "defaultTab": "<label of default tab>",
  "parentId": "0",
  "leftColumn": 9,
  "rightColumn": 64,
  "topRow": 5,
  "bottomRow": 67,
  "dynamicHeight": "AUTO_HEIGHT",
  "dynamicBindingPathList": [
    {"key": "accentColor"},
    {"key": "boxShadow"}
  ],
  "accentColor": "{{appsmith.theme.colors.primaryColor}}",
  "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}",
  "backgroundColor": "#FFFFFF",
  "borderColor": "#E0DEDE",
  "borderRadius": "0.375rem",
  "borderWidth": 1
}

3. Create widgets inside each tab

Each widget inside a tab sets "parentId" to that tab's canvas widgetId (not the Tabs widgetId). Widgets start at topRow: 0 within each canvas.

Table-only tabs (no date pickers):

{
  "type": "TABLE_WIDGET_V2",
  "parentId": "<canvas-widgetId>",
  "topRow": 0,
  "bottomRow": 58,
  "leftColumn": 0,
  "rightColumn": 62,
  "tableData": "{{<query_name>.data}}",
  ...
}

Tabs with date pickers (see Sales - Units Shipped for reference):

Widget topRow bottomRow leftColumn rightColumn
DateFrom 0 7 0 5
DateTo 0 7 5 10
Table 7 58 0 62

Date pickers use "type": "DATE_PICKER_WIDGET2", "dateFormat": "YYYY-MM-DD HH:mm", and "parentId" set to the tab's canvas widgetId.

4. Create queries for each tab

Follow the add-query-to-table skill. Each query's metadata.json must have:

  • "pageId": the page name (e.g. "Pending POs")
  • "gitSyncId": use the same 24-char hex prefix as the page, with a unique UUID suffix
  • "runBehaviour": "AUTOMATIC"

5. Delete the old standalone widget

If the page had a widgets/Table1.json (or similar) that the Tabs widget replaces, delete it.

6. Verify

  • Each canvas widgetId in children matches the corresponding widgetId in tabsObj.
  • Each widget inside a tab has parentId set to its tab's canvas widgetId.
  • defaultTab matches one of the tab labels (not the tab ID).
  • All queries reference the correct pageId.
  • Run git status / git diff to confirm only intended changes.

Canvas Widget Template

Each canvas child in the children array follows this template:

{
  "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
  "bottomRow": 620,
  "boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}",
  "canExtend": true,
  "detachFromLayout": true,
  "dynamicBindingPathList": [
    {"key": "borderRadius"},
    {"key": "boxShadow"}
  ],
  "dynamicHeight": "AUTO_HEIGHT",
  "dynamicTriggerPathList": [],
  "flexLayers": [],
  "isDisabled": false,
  "isLoading": false,
  "isVisible": true,
  "key": "<unique-key>",
  "leftColumn": 0,
  "maxDynamicHeight": 9000,
  "minDynamicHeight": 4,
  "minHeight": 150,
  "minWidth": 450,
  "mobileBottomRow": 150,
  "mobileLeftColumn": 0,
  "mobileRightColumn": 602.625,
  "mobileTopRow": 0,
  "needsErrorInfo": false,
  "parentColumnSpace": 1,
  "parentId": "<tabs-widgetId>",
  "parentRowSpace": 1,
  "renderMode": "CANVAS",
  "responsiveBehavior": "fill",
  "rightColumn": 602.625,
  "shouldScrollContents": false,
  "tabId": "<tab-id>",
  "tabName": "<tab-label>",
  "topRow": 0,
  "type": "CANVAS_WIDGET",
  "version": 1,
  "widgetId": "<unique-canvas-widgetId>",
  "widgetName": "Canvas<N>"
}

Consolidating Multiple Pages into Tabs

When merging separate pages into one tabbed page:

  1. Choose the surviving page (or create a new one via create-new-page skill).
  2. Move each page's query into the surviving page's queries/ directory. Update pageId and generate new gitSyncId values (same 24-char prefix as the surviving page).
  3. Create the Tabs widget with one tab per former page, plus any additional tabs (e.g. "All").
  4. Delete the old page directories entirely.
  5. Remove old navigation buttons from all remaining pages (delete the button JSON files).
  6. Update application.json to remove entries for deleted pages.
  7. Keep one nav button pointing to the surviving page.

Reference

Item Value
Widget type TABS_WIDGET version 3
Widget directory widgets/Tabs1/
Canvas parent Tabs widgetId
Widget-in-tab parent Canvas widgetId for that tab
defaultTab Tab label (not tab ID)
tabsObj keys tab1, tab2, tab3, ...
Existing examples Sales - Units Shipped, Pending POs