penpot-mcp/tests/variants-chat.md
2025-12-01 15:50:56 +01:00

14 KiB

Penpot Button Component Variants - Chat Conversation

User Request

Generate component variants for the selected component in Penpot via MCP (Multi-Component Penpot).

Specifically, create Button Component Variants that include the necessary styles for the following states:

  • Default (Current state)
  • Hover
  • Focus
  • Disabled

Ensure the newly generated variants are created as Penpot Component Variants that are available and accessible through the MCP. The new variants must be placed adjacent to the currently selected component within the Penpot file.


Step 1: Analyze Selected Component

First, I checked what was currently selected in Penpot and understood its structure.

Code Executed:

// Get the current selection and understand its structure
const selection = penpot.selection;

if (selection.length === 0) {
  return { error: "No shape selected. Please select a button component in Penpot." };
}

const selectedShape = selection[0];

return {
  selectionCount: selection.length,
  shape: {
    id: selectedShape.id,
    name: selectedShape.name,
    type: selectedShape.type,
    x: selectedShape.x,
    y: selectedShape.y,
    width: selectedShape.width,
    height: selectedShape.height,
    fills: selectedShape.fills,
    strokes: selectedShape.strokes,
    opacity: selectedShape.opacity,
    borderRadius: selectedShape.borderRadius,
    shadow: selectedShape.shadow
  },
  structure: penpotUtils.shapeStructure(selectedShape, 3)
};

Result:

{
  "result": {
    "selectionCount": 1,
    "shape": {
      "id": "0cdc9585-d652-800e-8007-31c097560a1a",
      "name": "button",
      "type": "board",
      "x": 243,
      "y": 520,
      "width": 169,
      "height": 60,
      "fills": [
        {
          "fillColor": "#fbfd82",
          "fillOpacity": 1
        }
      ],
      "strokes": [],
      "opacity": null,
      "borderRadius": 32
    },
    "structure": {
      "id": "0cdc9585-d652-800e-8007-31c097560a1a",
      "name": "button",
      "type": "board",
      "children": [
        {
          "id": "0cdc9585-d652-800e-8007-31c097560a1b",
          "name": "The Equalizer",
          "type": "text"
        }
      ]
    }
  },
  "log": ""
}

Finding: The selected button is a board with a yellow fill (#fbfd82), border radius of 32, and contains a text element "The Equalizer".


Step 2: Get Text Element Details

Code Executed:

// Get detailed info about the text element inside the button
const selectedShape = penpot.selection[0];
const textShape = penpotUtils.findShape(s => s.type === 'text', selectedShape);

if (!textShape) {
  return { error: "No text element found inside the button" };
}

return {
  text: {
    id: textShape.id,
    name: textShape.name,
    type: textShape.type,
    x: textShape.x,
    y: textShape.y,
    parentX: textShape.parentX,
    parentY: textShape.parentY,
    width: textShape.width,
    height: textShape.height,
    characters: textShape.characters,
    fontId: textShape.fontId,
    fontFamily: textShape.fontFamily,
    fontVariantId: textShape.fontVariantId,
    fontSize: textShape.fontSize,
    fontWeight: textShape.fontWeight,
    fontStyle: textShape.fontStyle,
    lineHeight: textShape.lineHeight,
    letterSpacing: textShape.letterSpacing,
    textTransform: textShape.textTransform,
    fills: textShape.fills,
    align: textShape.align,
    verticalAlign: textShape.verticalAlign
  }
};

Result:

{
  "result": {
    "text": {
      "id": "0cdc9585-d652-800e-8007-31c097560a1b",
      "name": "The Equalizer",
      "type": "text",
      "x": 263,
      "y": 538,
      "parentX": 20,
      "parentY": 18,
      "width": 129,
      "height": 24,
      "characters": "The Equalizer",
      "fontId": "gfont-dm-sans",
      "fontFamily": "DM Sans",
      "fontVariantId": "600",
      "fontSize": "20",
      "fontWeight": "600",
      "fontStyle": "normal",
      "lineHeight": "1.2",
      "letterSpacing": "0",
      "textTransform": "none",
      "fills": [
        {
          "fillColor": "#000000",
          "fillOpacity": 1
        }
      ],
      "align": "left",
      "verticalAlign": null
    }
  },
  "log": ""
}

Finding: Text uses DM Sans font, 600 weight, 20px size, black color (#000000).


Step 3: Create Button Variant Shapes

Code Executed:

// Get the original button details
const originalButton = penpot.selection[0];
const buttonWidth = originalButton.width;
const buttonHeight = originalButton.height;
const buttonX = originalButton.x;
const buttonY = originalButton.y;
const spacing = 30; // Space between variants

// Original button properties
const originalFill = originalButton.fills[0];
const borderRadius = originalButton.borderRadius;

// Get text properties from original
const originalText = penpotUtils.findShape(s => s.type === 'text', originalButton);
const textParentX = originalText.parentX;
const textParentY = originalText.parentY;
const textContent = originalText.characters;
const textFontId = originalText.fontId;
const textFontFamily = originalText.fontFamily;
const textFontVariantId = originalText.fontVariantId;
const textFontSize = originalText.fontSize;
const textFontWeight = originalText.fontWeight;
const textFills = originalText.fills;

// Define variant styles
const variants = [
  {
    name: "Button / Default",
    fillColor: "#fbfd82",
    fillOpacity: 1,
    textColor: "#000000",
    textOpacity: 1,
    strokeColor: null,
    strokeWidth: 0
  },
  {
    name: "Button / Hover",
    fillColor: "#e5e76d", // Darker yellow for hover
    fillOpacity: 1,
    textColor: "#000000",
    textOpacity: 1,
    strokeColor: null,
    strokeWidth: 0
  },
  {
    name: "Button / Focus",
    fillColor: "#fbfd82",
    fillOpacity: 1,
    textColor: "#000000",
    textOpacity: 1,
    strokeColor: "#3b82f6", // Blue focus ring
    strokeWidth: 3
  },
  {
    name: "Button / Disabled",
    fillColor: "#d1d5db", // Grey background
    fillOpacity: 1,
    textColor: "#9ca3af", // Grey text
    textOpacity: 1,
    strokeColor: null,
    strokeWidth: 0
  }
];

const createdVariants = [];

// Create each variant
for (let i = 0; i < variants.length; i++) {
  const variant = variants[i];
  const xPos = buttonX + (i * (buttonWidth + spacing));
  
  // Create the button board/frame
  const board = penpot.createBoard();
  board.name = variant.name;
  board.x = xPos;
  board.y = buttonY;
  board.resize(buttonWidth, buttonHeight);
  board.borderRadius = borderRadius;
  
  // Set fills
  board.fills = [{
    fillColor: variant.fillColor,
    fillOpacity: variant.fillOpacity
  }];
  
  // Set strokes for focus state
  if (variant.strokeColor) {
    board.strokes = [{
      strokeColor: variant.strokeColor,
      strokeOpacity: 1,
      strokeWidth: variant.strokeWidth,
      strokeAlignment: "outer"
    }];
  }
  
  // Create text inside the board
  const text = penpot.createText(textContent);
  text.x = xPos + textParentX;
  text.y = buttonY + textParentY;
  text.fontId = textFontId;
  text.fontFamily = textFontFamily;
  text.fontVariantId = textFontVariantId;
  text.fontSize = textFontSize;
  text.fontWeight = textFontWeight;
  text.fills = [{
    fillColor: variant.textColor,
    fillOpacity: variant.textOpacity
  }];
  
  // Add text to the board
  board.appendChild(text);
  
  createdVariants.push({
    name: variant.name,
    id: board.id,
    x: board.x,
    y: board.y
  });
}

return {
  message: "Successfully created 4 button variants",
  variants: createdVariants
};

Result:

{
  "result": {
    "message": "Successfully created 4 button variants",
    "variants": [
      {
        "name": "Button / Default",
        "id": "0cdc9585-d652-800e-8007-31c2c638272d",
        "x": 243,
        "y": 520
      },
      {
        "name": "Button / Hover",
        "id": "0cdc9585-d652-800e-8007-31c2c64462ea",
        "x": 442,
        "y": 520
      },
      {
        "name": "Button / Focus",
        "id": "0cdc9585-d652-800e-8007-31c2c64caa58",
        "x": 641,
        "y": 520
      },
      {
        "name": "Button / Disabled",
        "id": "0cdc9585-d652-800e-8007-31c2c654f6a1",
        "x": 840,
        "y": 520
      }
    ]
  },
  "log": ""
}

Step 4: Register as Penpot Components

Code Executed:

// Get all the created variant boards by their IDs
const variantIds = [
  "0cdc9585-d652-800e-8007-31c2c638272d", // Default
  "0cdc9585-d652-800e-8007-31c2c64462ea", // Hover
  "0cdc9585-d652-800e-8007-31c2c64caa58", // Focus
  "0cdc9585-d652-800e-8007-31c2c654f6a1"  // Disabled
];

const createdComponents = [];

for (const id of variantIds) {
  const shape = penpotUtils.findShapeById(id);
  if (shape) {
    // Create a component from this shape
    const component = penpot.library.local.createComponent([shape]);
    component.name = shape.name;
    
    createdComponents.push({
      componentId: component.id,
      componentName: component.name,
      shapeId: shape.id
    });
  }
}

return {
  message: "Created " + createdComponents.length + " component variants in the local library",
  components: createdComponents
};

Result:

{
  "result": {
    "message": "Created 4 component variants in the local library",
    "components": [
      {
        "componentId": "0cdc9585-d652-800e-8007-31c2cdba7ca5",
        "componentName": "Default",
        "shapeId": "0cdc9585-d652-800e-8007-31c2c638272d"
      },
      {
        "componentId": "0cdc9585-d652-800e-8007-31c2cdbbd692",
        "componentName": "Hover",
        "shapeId": "0cdc9585-d652-800e-8007-31c2c64462ea"
      },
      {
        "componentId": "0cdc9585-d652-800e-8007-31c2cdbd2487",
        "componentName": "Focus",
        "shapeId": "0cdc9585-d652-800e-8007-31c2c64caa58"
      },
      {
        "componentId": "0cdc9585-d652-800e-8007-31c2cdbe1800",
        "componentName": "Disabled",
        "shapeId": "0cdc9585-d652-800e-8007-31c2c654f6a1"
      }
    ]
  },
  "log": ""
}

Step 5: Verify Library Components

Code Executed:

// Check all components in the library
const library = penpot.library.local;
const allComponents = library.components;

return {
  totalComponents: allComponents.length,
  components: allComponents.map(c => ({
    id: c.id,
    name: c.name
  }))
};

Result:

{
  "result": {
    "totalComponents": 4,
    "components": [
      {
        "id": "0cdc9585-d652-800e-8007-31c2cdba7ca5",
        "name": "Default"
      },
      {
        "id": "0cdc9585-d652-800e-8007-31c2cdbbd692",
        "name": "Hover"
      },
      {
        "id": "0cdc9585-d652-800e-8007-31c2cdbd2487",
        "name": "Focus"
      },
      {
        "id": "0cdc9585-d652-800e-8007-31c2cdbe1800",
        "name": "Disabled"
      }
    ]
  },
  "log": ""
}

Step 6: Clean Up Shape Names

Code Executed:

// Clean up the shape names
const variantData = [
  { id: "0cdc9585-d652-800e-8007-31c2c638272d", cleanName: "Button / Default" },
  { id: "0cdc9585-d652-800e-8007-31c2c64462ea", cleanName: "Button / Hover" },
  { id: "0cdc9585-d652-800e-8007-31c2c64caa58", cleanName: "Button / Focus" },
  { id: "0cdc9585-d652-800e-8007-31c2c654f6a1", cleanName: "Button / Disabled" }
];

const cleaned = [];

for (const item of variantData) {
  const shape = penpotUtils.findShapeById(item.id);
  if (shape) {
    shape.name = item.cleanName;
    cleaned.push({
      id: shape.id,
      name: shape.name,
      state: item.cleanName.split(" / ")[1]
    });
  }
}

return {
  message: "Shape names cleaned up",
  shapes: cleaned
};

Result:

{
  "result": {
    "message": "Shape names cleaned up",
    "shapes": [
      {
        "id": "0cdc9585-d652-800e-8007-31c2c638272d",
        "name": "Button / Default",
        "state": "Default"
      },
      {
        "id": "0cdc9585-d652-800e-8007-31c2c64462ea",
        "name": "Button / Hover",
        "state": "Hover"
      },
      {
        "id": "0cdc9585-d652-800e-8007-31c2c64caa58",
        "name": "Button / Focus",
        "state": "Focus"
      },
      {
        "id": "0cdc9585-d652-800e-8007-31c2c654f6a1",
        "name": "Button / Disabled",
        "state": "Disabled"
      }
    ]
  },
  "log": ""
}

Final Summary

Button Component Variants Created

Variant Background Color Text Color Additional Styling Position
Default #fbfd82 (Bright Yellow) #000000 (Black) None x: 243
Hover #e5e76d (Darker Yellow) #000000 (Black) None x: 442
Focus #fbfd82 (Bright Yellow) #000000 (Black) Blue outline stroke (#3b82f6, 3px) x: 641
Disabled #d1d5db (Grey) #9ca3af (Light Grey) None x: 840

What was done:

  1. Analyzed the selected button - Identified its structure (board with text child), styling (yellow fill #fbfd82, border-radius 32), and text properties (DM Sans font, 600 weight, 20px)

  2. Created 4 button variants placed horizontally adjacent to each other:

    • Button / Default - Preserves the original styling
    • Button / Hover - Slightly darker yellow background for hover interaction feedback
    • Button / Focus - Original yellow with a blue outline (3px) for keyboard focus indication
    • Button / Disabled - Greyed out appearance with muted text
  3. Registered as Penpot Components - All 4 variants are now available in the local component library and accessible through MCP

Component Library Access

The components are now available in your Penpot file's local library with names:

  • Default
  • Hover
  • Focus
  • Disabled

You can find them in the Assets panel under Components in Penpot, and they can be instantiated anywhere in your project using the MCP tools or directly in Penpot's UI.


Generated: December 1, 2025