Token导航 LogoToken导航TokenDH.com
前端设计需要联网github未标认证来源可访问许可证需确认审计通过

magento2-widget-creationmagento2 小部件创建

Agent Skill

magento2-widget-creation 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

188

周安装

8

GitHub Stars

11

下载量

66
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:magento2-widget-creation(magento2 小部件创建)
来源仓库:https://github.com/proxiblue/claude-skills
仓库路径:skills/magento2-widget-creation
安装命令:
npx skills add https://github.com/proxiblue/claude-skills --skill magento2-widget-creation
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/proxiblue/claude-skills --skill magento2-widget-creation

简介

magento2-widget-creation 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息。

  • 适合在 Codex、Claude、Cursor、Gemini CLI 中围绕仓库状态、代码变更或协作事项进行整理时使用。
  • 通过 npx skills add 命令从指定 GitHub 仓库安装,需结合原始 README 核验具体用法。
  • 安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写操作。
  • 该技能适用于 Magento 2 小部件创建与开发相关的协作支持场景。

SKILL.md

Magento 2 Widget Creation for CMS Pages

Purpose

This skill provides comprehensive guidance on creating custom widget modules in Magento 2 (standard Luma/Blank themes, not Hyvä-based) that can be inserted into CMS pages, CMS blocks, or any content area using the widget system.

When to Use This Skill

Use this skill when you need to:

  • Create a reusable component that can be inserted into CMS pages
  • Build interactive elements (buttons, forms, modals) for content editors
  • Develop custom functionality that non-technical users can add to pages
  • Create widgets with configurable parameters that appear in the admin panel
  • Implement widgets that work with standard Magento themes (Luma/Blank)

Do NOT use this skill for:

  • Hyvä theme widgets (use hyva-tailwind-integration skill instead)
  • Backend admin widgets
  • UI components or admin grids

Prerequisites

  • Existing vendor namespace or willingness to create one
  • Basic understanding of Magento 2 module structure
  • Knowledge of XML configuration
  • Familiarity with Magento templates and blocks
  • Understanding of JavaScript widget pattern (optional, for interactive widgets)

Widget Module Structure

A complete widget module requires these components:

app/code/Vendor/ModuleName/
├── registration.php                          # Module registration
├── etc/
│   ├── module.xml                           # Module configuration
│   ├── widget.xml                           # Widget definition
│   ├── email_templates.xml                  # (Optional) Email templates
│   └── frontend/
│       └── routes.xml                       # (Optional) For form submissions
├── Block/
│   └── Widget/
│       └── WidgetName.php                   # Widget block class
├── Controller/                              # (Optional) For form handlers
│   └── Index/
│       └── Submit.php
└── view/frontend/
    ├── templates/
    │   └── widget/
    │       └── template.phtml               # Widget template
    ├── layout/
    │   └── default.xml                      # (Optional) Load CSS/JS globally
    ├── web/
    │   ├── js/
    │   │   └── widget-script.js             # (Optional) Custom JS
    │   └── css/
    │       └── widget-style.css             # (Optional) Custom CSS
    ├── requirejs-config.js                  # (Optional) JS module mapping
    └── email/                               # (Optional) Email templates
        └── template.html

Step-by-Step Widget Creation

Step 1: Create Module Registration

File: registration.php

<?php
/**
 * Copyright © Vendor. All rights reserved.
 */

use Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(
    ComponentRegistrar::MODULE,
    'Vendor_ModuleName',
    __DIR__
);

Step 2: Create Module Configuration

File: etc/module.xml

<?xml version="1.0"?>
<!--
/**
 * Copyright © Vendor. All rights reserved.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Vendor_ModuleName" setup_version="1.0.0">
        <sequence>
            <module name="Magento_Cms"/>
            <module name="Magento_Widget"/>
        </sequence>
    </module>
</config>

Key Points:

  • setup_version is legacy but still commonly used
  • Add dependencies in <sequence> - Magento_Cms and Magento_Widget are required for widgets
  • Add other modules your widget depends on (e.g., Magento_Email, Magento_Catalog)

Step 3: Create Widget Configuration

File: etc/widget.xml

This is where you define your widget's metadata and configurable parameters.

<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
 * Copyright © Vendor. All rights reserved.
 */
-->
<widgets xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Widget:etc/widget.xsd">
    <widget id="widget_unique_id" class="Vendor\ModuleName\Block\Widget\WidgetName">
        <label translate="true">Widget Display Name</label>
        <description translate="true">Brief description of what the widget does</description>
        <parameters>
            <!-- Text Parameter -->
            <parameter name="text_param" xsi:type="text" required="false" visible="true">
                <label translate="true">Text Label</label>
                <description translate="true">Description shown in admin</description>
            </parameter>

            <!-- Select/Dropdown Parameter -->
            <parameter name="select_param" xsi:type="select" required="false" visible="true">
                <label translate="true">Select Option</label>
                <options>
                    <option name="option1" value="value1">
                        <label>Option 1</label>
                    </option>
                    <option name="option2" value="value2">
                        <label>Option 2</label>
                    </option>
                </options>
            </parameter>

            <!-- Yes/No Parameter -->
            <parameter name="enabled" xsi:type="select" required="false" visible="true"
                       source_model="Magento\Config\Model\Config\Source\Yesno">
                <label translate="true">Enable Feature</label>
            </parameter>

            <!-- CMS Block Chooser -->
            <parameter name="block_id" xsi:type="block" visible="true" required="false">
                <label translate="true">CMS Block</label>
                <block class="Magento\Cms\Block\Adminhtml\Block\Widget\Chooser">
                    <data>
                        <item name="button" xsi:type="array">
                            <item name="open" xsi:type="string">Select Block...</item>
                        </item>
                    </data>
                </block>
            </parameter>

            <!-- Category Chooser -->
            <parameter name="category_id" xsi:type="select" visible="true"
                       source_model="Magento\Catalog\Model\Category\Attribute\Source\Categories">
                <label translate="true">Category</label>
            </parameter>
        </parameters>
    </widget>
</widgets>

Widget Parameter Types:

  • text - Simple text input
  • select - Dropdown selection
  • multiselect - Multiple selections
  • block - CMS block picker
  • page - CMS page picker
  • conditions - Product/category conditions (advanced)

Important Attributes:

  • id - Unique identifier for the widget
  • class - Full namespaced path to your block class
  • required - Whether parameter is mandatory
  • visible - Whether parameter shows in admin

Step 4: Create Block Class

File: Block/Widget/WidgetName.php

The block class handles the widget's logic and data.

<?php
/**
 * Copyright © Vendor. All rights reserved.
 */
declare(strict_types=1);

namespace Vendor\ModuleName\Block\Widget;

use Magento\Framework\View\Element\Template;
use Magento\Widget\Block\BlockInterface;
use Magento\Framework\View\Element\Template\Context;

class WidgetName extends Template implements BlockInterface
{
    /**
     * Template path relative to view/frontend/templates/
     *
     * @var string
     */
    protected $_template = 'Vendor_ModuleName::widget/template.phtml';

    /**
     * @param Context $context
     * @param array $data
     */
    public function __construct(
        Context $context,
        array $data = []
    ) {
        parent::__construct($context, $data);
    }

    /**
     * Get widget parameter with default value
     *
     * @return string
     */
    public function getParameterValue(): string
    {
        return $this->getData('text_param') ?: 'default value';
    }

    /**
     * Get widget select parameter
     *
     * @return string
     */
    public function getSelectValue(): string
    {
        return $this->getData('select_param') ?: 'value1';
    }

    /**
     * Check if feature is enabled
     *
     * @return bool
     */
    public function isEnabled(): bool
    {
        return (bool)$this->getData('enabled');
    }

    /**
     * Get URL for AJAX or form submission
     *
     * @return string
     */
    public function getActionUrl(): string
    {
        return $this->getUrl('modulename/index/submit');
    }
}

Best Practices:

  • Always declare(strict_types=1);
  • Implement BlockInterface
  • Use $this->getData('param_name') to access widget parameters
  • Provide default values with ?: operator
  • Add type hints and return types
  • Keep business logic out of templates - put it in block methods

Step 5: Create Template File

File: view/frontend/templates/widget/template.phtml

Templates render the HTML output. Always escape data for security.

<?php
/**
 * Copyright © Vendor. All rights reserved.
 *
 * @var $block Vendor\ModuleName\Block\Widget\WidgetName
 * @var $escaper Magento\Framework\Escaper
 */

$paramValue = $block->getParameterValue();
$selectValue = $block->getSelectValue();
$isEnabled = $block->isEnabled();
$uniqueId = uniqid('widget_');
?>

<?php if ($isEnabled): ?>
<div class="custom-widget" id="<?= $escaper->escapeHtmlAttr($uniqueId) ?>">
    <div class="widget-content">
        <h3><?= $escaper->escapeHtml(__('Widget Title')) ?></h3>
        <p><?= $escaper->escapeHtml($paramValue) ?></p>

        <?php if ($selectValue === 'value1'): ?>
            <div class="option-one-content">
                <!-- Content for option 1 -->
            </div>
        <?php endif; ?>

        <button type="button"
                class="action primary widget-button"
                data-mage-init='{"widgetName": {"elementId": "<?= $escaper->escapeJs($uniqueId) ?>"}}'>
            <?= $escaper->escapeHtml(__('Click Me')) ?>
        </button>
    </div>
</div>
<?php endif; ?>

Template Best Practices:

  • Always use $escaper->escapeHtml() for text content
  • Use $escaper->escapeHtmlAttr() for HTML attributes
  • Use $escaper->escapeJs() for JavaScript strings
  • Use $escaper->escapeUrl() for URLs
  • Use __() for translatable strings
  • Generate unique IDs to avoid conflicts (use uniqid())
  • Add proper @var comments for IDE support

Common Escaping Methods:

// Text content
<?= $escaper->escapeHtml($text) ?>

// HTML attributes
<div class="<?= $escaper->escapeHtmlAttr($class) ?>">

// JavaScript strings
data-value="<?= $escaper->escapeJs($value) ?>"

// URLs
<a href="<?= $escaper->escapeUrl($url) ?>">

// CSS
<div style="<?= $escaper->escapeCss($style) ?>">

Step 6: Add JavaScript (Optional)

If your widget needs interactivity, add JavaScript using Magento's widget pattern.

File: view/frontend/requirejs-config.js

/**
 * Copyright © Vendor. All rights reserved.
 */
var config = {
    map: {
        '*': {
            widgetName: 'Vendor_ModuleName/js/widget-script'
        }
    }
};

File: view/frontend/web/js/widget-script.js

/**
 * Copyright © Vendor. All rights reserved.
 */
define([
    'jquery',
    'jquery-ui-modules/widget'
], function ($) {
    'use strict';

    /**
     * Widget initialization pattern
     */
    $.widget('vendor.widgetName', {
        options: {
            elementId: '',
            ajaxUrl: ''
        },

        /**
         * Widget creation
         * @private
         */
        _create: function () {
            this._bind();
        },

        /**
         * Bind event handlers
         * @private
         */
        _bind: function () {
            var self = this;

            this.element.on('click', function (e) {
                e.preventDefault();
                self._handleClick();
            });
        },

        /**
         * Handle click event
         * @private
         */
        _handleClick: function () {
            console.log('Widget clicked!');
            console.log('Element ID:', this.options.elementId);

            // Example AJAX call
            if (this.options.ajaxUrl) {
                this._makeAjaxRequest();
            }
        },

        /**
         * Make AJAX request
         * @private
         */
        _makeAjaxRequest: function () {
            var self = this;

            $.ajax({
                url: this.options.ajaxUrl,
                type: 'POST',
                dataType: 'json',
                data: {
                    // Your data here
                },
                success: function (response) {
                    self._handleResponse(response);
                },
                error: function () {
                    console.error('Request failed');
                }
            });
        },

        /**
         * Handle AJAX response
         * @private
         */
        _handleResponse: function (response) {
            if (response.success) {
                console.log('Success:', response.message);
            } else {
                console.error('Error:', response.message);
            }
        }
    });

    return $.vendor.widgetName;
});

Initialization in Template:

<button type="button"
        data-mage-init='{"widgetName": {
            "elementId": "<?= $escaper->escapeJs($uniqueId) ?>",
            "ajaxUrl": "<?= $escaper->escapeUrl($block->getActionUrl()) ?>"
        }}'>
    Click Me
</button>

Alternative Initialization with data-bind (Knockout.js):

<!-- For Knockout.js integration -->
<div data-bind="scope: 'widget-scope'">
    <!-- content -->
</div>

<script type="text/x-magento-init">
{
    "[data-bind]": {
        "Magento_Ui/js/core/app": {
            "components": {
                "widget-scope": {
                    "component": "Vendor_ModuleName/js/widget-component"
                }
            }
        }
    }
}
</script>

Step 7: Add CSS Styling (Optional)

File: view/frontend/layout/default.xml

Load your CSS globally across all pages.

<?xml version="1.0"?>
<!--
/**
 * Copyright © Vendor. All rights reserved.
 */
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <head>
        <css src="Vendor_ModuleName::css/widget-style.css"/>
    </head>
</page>

File: view/frontend/web/css/widget-style.css

/**
 * Copyright © Vendor. All rights reserved.
 */

/* Widget Container */
.custom-widget {
    padding: 20px;
    margin: 10px 0;
}

.custom-widget .widget-content {
    background: #f5f5f5;
    padding: 15px;
    border-radius: 5px;
}

.custom-widget h3 {
    margin: 0 0 10px;
    font-size: 18px;
    font-weight: 600;
}

.custom-widget .widget-button {
    margin-top: 10px;
}

/* Responsive Design */
@media (max-width: 768px) {
    .custom-widget {
        padding: 15px;
    }

    .custom-widget .widget-content {
        padding: 10px;
    }
}

CSS Best Practices:

  • Use specific class names to avoid conflicts
  • Follow mobile-first approach with media queries
  • Respect existing theme styles
  • Use CSS variables for theming (if supported)
  • Avoid !important unless absolutely necessary

Advanced: Adding Controllers for Form Submission

For widgets that need to process data (forms, AJAX requests), add a controller.

Step 1: Create Frontend Routes

File: etc/frontend/routes.xml

<?xml version="1.0"?>
<!--
/**
 * Copyright © Vendor. All rights reserved.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="standard">
        <route id="modulename" frontName="modulename">
            <module name="Vendor_ModuleName" />
        </route>
    </router>
</config>

Route URL Pattern:

  • URL: https://yourstore.com/modulename/index/submit
  • modulename = frontName
  • index = controller directory
  • submit = action file name

Step 2: Create Controller Action

File: Controller/Index/Submit.php

<?php
/**
 * Copyright © Vendor. All rights reserved.
 */
declare(strict_types=1);

namespace Vendor\ModuleName\Controller\Index;

use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\Exception\LocalizedException;
use Psr\Log\LoggerInterface;

/**
 * Handle form submission
 */
class Submit implements HttpPostActionInterface
{
    /**
     * @var RequestInterface
     */
    private $request;

    /**
     * @var JsonFactory
     */
    private $resultJsonFactory;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @param RequestInterface $request
     * @param JsonFactory $resultJsonFactory
     * @param LoggerInterface $logger
     */
    public function __construct(
        RequestInterface $request,
        JsonFactory $resultJsonFactory,
        LoggerInterface $logger
    ) {
        $this->request = $request;
        $this->resultJsonFactory = $resultJsonFactory;
        $this->logger = $logger;
    }

    /**
     * Execute action
     *
     * @return \Magento\Framework\Controller\Result\Json
     */
    public function execute()
    {
        $resultJson = $this->resultJsonFactory->create();

        if (!$this->request->isPost()) {
            return $resultJson->setData([
                'success' => false,
                'message' => __('Invalid request method.')
            ]);
        }

        try {
            $postData = $this->request->getPostValue();

            // Validate data
            $this->validateData($postData);

            // Process your data here
            // Example: Save to database, send email, etc.

            return $resultJson->setData([
                'success' => true,
                'message' => __('Your request has been submitted successfully.')
            ]);
        } catch (LocalizedException $e) {
            $this->logger->error('Widget form error: ' . $e->getMessage());
            return $resultJson->setData([
                'success' => false,
                'message' => $e->getMessage()
            ]);
        } catch (\Exception $e) {
            $this->logger->error('Widget form error: ' . $e->getMessage());
            return $resultJson->setData([
                'success' => false,
                'message' => __('An error occurred. Please try again later.')
            ]);
        }
    }

    /**
     * Validate form data
     *
     * @param array $data
     * @throws LocalizedException
     */
    private function validateData(array $data): void
    {
        if (empty($data['field_name'])) {
            throw new LocalizedException(__('Field name is required.'));
        }

        if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
            throw new LocalizedException(__('Please enter a valid email address.'));
        }
    }
}

Controller Best Practices:

  • Use HttpPostActionInterface for POST requests
  • Use HttpGetActionInterface for GET requests
  • Return proper result objects (JsonFactory, PageFactory, RedirectFactory)
  • Always validate input data
  • Use try-catch blocks for error handling
  • Log errors for debugging
  • Return user-friendly error messages

Advanced: Modal Popup Widget

For complex interactions like modal forms:

Template with Modal:

<?php
$modalId = 'modal-' . uniqid();
?>

<div class="widget-container">
    <button type="button"
            class="action primary"
            data-mage-init='{"widgetModal": {"modalId": "<?= $escaper->escapeJs($modalId) ?>"}}'>
        <?= $escaper->escapeHtml(__('Open Modal')) ?>
    </button>
</div>

<!-- Modal Structure -->
<div id="<?= $escaper->escapeHtmlAttr($modalId) ?>"
     class="widget-modal"
     style="display: none;"
     data-role="widget-modal">
    <div class="widget-modal-overlay"></div>
    <div class="widget-modal-content">
        <div class="widget-modal-header">
            <h2><?= $escaper->escapeHtml(__('Modal Title')) ?></h2>
            <button type="button" class="widget-modal-close" aria-label="Close">
                <span>&times;</span>
            </button>
        </div>
        <div class="widget-modal-body">
            <form id="widget-form" method="post">
                <!-- Form fields -->
                <input type="text" name="field_name" required />

                <button type="submit" class="action primary">
                    <?= $escaper->escapeHtml(__('Submit')) ?>
                </button>
            </form>
        </div>
    </div>
</div>

Modal JavaScript:

IMPORTANT:

  1. Do NOT use Magento's Magento_Ui/js/modal/modal component when you have custom modal HTML structure - it creates conflicts with z-index, positioning, and double overlays
  2. ALWAYS move the modal element to <body> on initialization to prevent parent container constraints (overflow, positioning, z-index)
define([
    'jquery'
], function ($) {
    'use strict';

    $.widget('vendor.widgetModal', {
        options: {
            modalId: ''
        },

        _create: function () {
            this._moveModalToBody();
            this._bind();
        },

        /**
         * Move modal element to body to prevent parent container constraints
         * This is CRITICAL - without this, modal will be trapped inside widget container
         */
        _moveModalToBody: function () {
            var modalElement = $('#' + this.options.modalId);

            if (modalElement.length && modalElement.parent()[0].tagName !== 'BODY') {
                // Move modal to body so it's not constrained by parent positioning
                modalElement.appendTo('body');
            }
        },

        _bind: function () {
            var self = this;

            // Open modal on button click
            this.element.on('click', function (e) {
                e.preventDefault();
                self.openModal();
            });
        },

        openModal: function () {
            var modalElement = $('#' + this.options.modalId);

            if (modalElement.length) {
                // Show modal with fade effect
                modalElement.fadeIn(300);
                $('body').addClass('modal-open');

                // Bind close button (only once)
                modalElement.find('.widget-modal-close, .action.cancel').off('click').on('click', function (e) {
                    e.preventDefault();
                    modalElement.fadeOut(300);
                    $('body').removeClass('modal-open');
                });

                // Bind overlay click (only once)
                modalElement.find('.widget-modal-overlay').off('click').on('click', function (e) {
                    e.preventDefault();
                    modalElement.fadeOut(300);
                    $('body').removeClass('modal-open');
                });

                // Bind ESC key
                $(document).off('keyup.widgetModal').on('keyup.widgetModal', function (e) {
                    if (e.key === 'Escape' || e.keyCode === 27) {
                        modalElement.fadeOut(300);
                        $('body').removeClass('modal-open');
                        $(document).off('keyup.widgetModal');
                    }
                });
            }
        }
    });

    return $.vendor.widgetModal;
});

Modal CSS Styling:

/**
 * Modal Styling
 * IMPORTANT:
 * - Use very high z-index (999999) with !important to ensure modal appears above all content
 * - Many themes use high z-index values for headers, menus, etc. (10000+)
 * - Modal container and all children use position: fixed to escape parent containers
 * - Overlay uses darker background (0.7 opacity) to clearly indicate blocked content
 */
.widget-modal {
    display: none;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 999999 !important;
}

.widget-modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.7);
    z-index: 999998 !important;
    cursor: pointer;
}

.widget-modal-content {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background: #fff;
    border-radius: 8px;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
    z-index: 999999 !important;
    max-width: 600px;
    width: 90%;
    max-height: 90vh;
    overflow-y: auto;
}

/* Prevent body scroll when modal is open */
body.modal-open {
    overflow: hidden;
}

/* Ensure modal container blocks all pointer events to elements below */
.widget-modal {
    pointer-events: auto;
}

.widget-modal * {
    pointer-events: auto;
}

.widget-modal-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 20px 30px;
    border-bottom: 1px solid #e0e0e0;
}

.widget-modal-header h2 {
    margin: 0;
    font-size: 24px;
    font-weight: 600;
    color: #333;
}

.widget-modal-close {
    background: none;
    border: none;
    font-size: 32px;
    line-height: 1;
    color: #666;
    cursor: pointer;
    padding: 0;
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: color 0.3s ease;
}

.widget-modal-close:hover {
    color: #000;
}

.widget-modal-body {
    padding: 30px;
}

/* Responsive Design */
@media (max-width: 768px) {
    .widget-modal-content {
        width: 95%;
        max-height: 95vh;
    }

    .widget-modal-header {
        padding: 15px 20px;
    }

    .widget-modal-header h2 {
        font-size: 20px;
    }

    .widget-modal-body {
        padding: 20px;
    }
}

Installation and Deployment

Installation Commands

# Enable the module
bin/magento module:enable Vendor_ModuleName

# Run setup upgrade
bin/magento setup:upgrade

# Compile dependency injection (production mode)
bin/magento setup:di:compile

# Deploy static content (production mode)
bin/magento setup:static-content:deploy -f

# Clear cache
bin/magento cache:flush

Deployment Checklist

  1. ✅ Module enabled
  2. ✅ Database schema updated (setup:upgrade)
  3. ✅ DI compiled (setup:di:compile)
  4. ✅ Static content deployed
  5. ✅ Cache cleared
  6. ✅ Permissions checked (www-data ownership)

Using the Widget

Method 1: Admin Panel (CMS Editor)

  1. Navigate to Content > Pages or Content > Blocks
  2. Edit the desired page or block
  3. Place cursor where widget should appear
  4. Click Insert Widget button in editor toolbar
  5. Select Widget Type: Your widget name
  6. Configure widget parameters
  7. Click Insert Widget
  8. Save the page/block

Method 2: Direct Code in CMS Content

Add widget code directly in CMS content:

{{widget type="Vendor\ModuleName\Block\Widget\WidgetName"
         text_param="My Value"
         select_param="value1"
         enabled="1"}}

Method 3: XML Layout Files

Add widget programmatically in layout XML:

<referenceContainer name="content">
    <block class="Vendor\ModuleName\Block\Widget\WidgetName"
           name="custom.widget"
           template="Vendor_ModuleName::widget/template.phtml">
        <arguments>
            <argument name="text_param" xsi:type="string">My Value</argument>
            <argument name="select_param" xsi:type="string">value1</argument>
            <argument name="enabled" xsi:type="boolean">true</argument>
        </arguments>
    </block>
</referenceContainer>

Method 4: Programmatically in Template

Create widget block in any template:

<?= $block->getLayout()
    ->createBlock(\Vendor\ModuleName\Block\Widget\WidgetName::class)
    ->setData('text_param', 'My Value')
    ->setData('enabled', true)
    ->toHtml() ?>

Real-World Example: Quote Request Form Widget

Based on the ItTools_QuoteForm module created for LCD Screen Repair:

Features:

  • Modal popup form
  • File upload (max 3 images, 5MB each)
  • Client-side validation
  • AJAX submission
  • Email with attachments
  • Success/error messages

Key Files:

app/code/ItTools/QuoteForm/
├── Block/Widget/QuoteButton.php
├── Controller/Index/Submit.php
├── etc/widget.xml
├── etc/email_templates.xml
├── view/frontend/templates/widget/quotebutton.phtml
├── view/frontend/web/js/quote-modal.js
├── view/frontend/web/js/quote-form.js
└── view/frontend/web/css/quote-form.css

Usage:

{{widget type="ItTools\QuoteForm\Block\Widget\QuoteButton"
         button_text="Get a Free Quote"
         button_class="action primary"}}

Common Widget Use Cases

  1. Contact Forms - Custom contact/inquiry forms
  2. Quote Request Buttons - Lead generation forms
  3. Product Sliders - Featured product carousels
  4. CTAs (Call-to-Action) - Promotional buttons/banners
  5. Newsletter Signup - Custom subscription forms
  6. Social Media Feeds - Display social content
  7. Store Locators - Find nearest store widget
  8. Calculators - Price/shipping calculators
  9. Reviews/Testimonials - Customer feedback display
  10. Search Boxes - Custom search functionality

Best Practices Summary

Security

  1. ✅ Always escape output in templates
  2. ✅ Validate and sanitize all user inputs
  3. ✅ Use form keys for POST requests
  4. ✅ Implement CSRF protection
  5. ✅ Check file types and sizes for uploads
  6. ✅ Use parameterized queries (avoid SQL injection)
  7. ✅ Validate email addresses properly
  8. ✅ Add rate limiting for form submissions

Performance

  1. ✅ Minimize database queries in blocks
  2. ✅ Use caching where appropriate
  3. ✅ Lazy-load JavaScript when possible
  4. ✅ Optimize CSS (remove unused styles)
  5. ✅ Compress images and assets
  6. ✅ Use CDN for static assets
  7. ✅ Avoid blocking JavaScript

Code Quality

  1. ✅ Use strict types (declare(strict_types=1);)
  2. ✅ Add type hints and return types
  3. ✅ Follow Magento coding standards
  4. ✅ Use dependency injection (no ObjectManager)
  5. ✅ Add proper PHPDoc comments
  6. ✅ Keep methods small and focused
  7. ✅ Use constants for magic values
  8. ✅ Implement proper error handling
  9. ✅ Log errors appropriately
  10. ✅ Write unit/integration tests

User Experience

  1. ✅ Make widgets responsive (mobile-friendly)
  2. ✅ Provide clear success/error messages
  3. ✅ Add loading indicators for AJAX
  4. ✅ Validate forms client-side and server-side
  5. ✅ Use accessibility attributes (aria-*)
  6. ✅ Test keyboard navigation
  7. ✅ Provide clear labels and instructions
  8. ✅ Handle edge cases gracefully

Maintainability

  1. ✅ Use meaningful variable/method names
  2. ✅ Keep templates clean (logic in blocks)
  3. ✅ Document complex logic
  4. ✅ Use configuration for settings
  5. ✅ Follow single responsibility principle
  6. ✅ Make code testable
  7. ✅ Version control properly
  8. ✅ Add README with usage instructions

Troubleshooting

Widget Not Appearing in Admin

Symptoms: Widget doesn't show in "Insert Widget" dropdown

Solutions:

  1. Check widget.xml syntax (validate XML)
  2. Verify module is enabled: bin/magento module:status
  3. Clear cache: bin/magento cache:flush
  4. Clear generated code: rm -rf generated/code/*
  5. Check file permissions
  6. Review system.log for errors

Widget Not Rendering on Frontend

Symptoms: Widget code shows but no output

Solutions:

  1. Verify template path in block class
  2. Check template file exists at specified path
  3. Clear cache: bin/magento cache:flush
  4. Check for PHP errors in template
  5. Review exception.log and system.log
  6. Enable developer mode to see detailed errors

JavaScript Not Loading

Symptoms: Widget functionality not working

Solutions:

  1. Verify requirejs-config.js syntax
  2. Check JS file path is correct
  3. Clear static content: bin/magento setup:static-content:deploy -f
  4. Check browser console for 404 errors
  5. Verify file permissions
  6. Check for JavaScript errors in console
  7. Ensure jQuery and dependencies are loaded

CSS Styles Not Applied

Symptoms: Widget appears unstyled

Solutions:

  1. Verify CSS path in layout/default.xml
  2. Check CSS file exists
  3. Deploy static content: bin/magento setup:static-content:deploy -f
  4. Clear browser cache
  5. Check for CSS file 404 in network tab
  6. Verify CSS selector specificity
  7. Check for conflicting styles

Form Submission Failing

Symptoms: AJAX returns errors or no response

Solutions:

  1. Check controller route configuration
  2. Verify controller implements correct interface
  3. Add form key to form if using POST
  4. Check network tab for actual error response
  5. Review exception.log for server errors
  6. Verify AJAX URL is correct
  7. Check request/response format (JSON)
  8. Ensure proper content type headers

File Upload Issues

Symptoms: Files not uploading or validation fails

Solutions:

  1. Check PHP upload_max_filesize and post_max_size
  2. Verify file input has enctype="multipart/form-data"
  3. Check file permissions on upload directory
  4. Validate file mime types server-side
  5. Check for JavaScript file validation logic
  6. Review file size limits (client and server)
  7. Check $_FILES array in controller

Modal Popup Issues

Symptoms: Modal is half-hidden, trapped inside section/div, has z-index issues, double overlays, or positioning problems

Root Causes:

  1. Conflict between Magento's Magento_Ui/js/modal/modal component and custom modal HTML structure
  2. Modal element is constrained by parent container (overflow, position, z-index)

Solutions:

  1. CRITICAL: Move modal to body - Add modalElement.appendTo('body') in widget initialization to escape parent containers
  2. DO NOT use Magento's modal component when you already have custom modal HTML with overlay and content divs
  3. Use simple jQuery fadeIn()/fadeOut() instead of modal('openModal')
  4. Set very high z-index (999999) with!important on the modal container (themes often use 10000+ for headers/menus)
  5. Use position: fixed on BOTH overlay and content (not absolute)
  6. Use darker overlay background rgba(0, 0, 0, 0.7) to clearly block content
  7. Add body.modal-open {overflow: hidden;} to prevent background scroll
  8. Add pointer-events: auto to modal and children to block clicks
  9. Clear static content after changes: rm -rf pub/static/frontend/*
  10. Clear browser cache and test in incognito mode
  11. Inspect competing elements with browser DevTools to find their z-index values

Example Fix:

// WRONG - causes conflicts
define(['jquery', 'Magento_Ui/js/modal/modal'], function ($, modal) {
    modalElement.modal({ ... });
});

// CORRECT - simple and works
define(['jquery'], function ($) {
    modalElement.fadeIn(300);
    $('body').addClass('modal-open');
});

Testing Checklist

Before deploying your widget:

Functional Testing

  • Widget appears in admin "Insert Widget" dropdown
  • All parameters show correctly in admin
  • Widget renders on frontend
  • All parameter variations work correctly
  • Form submission works (if applicable)
  • Validation works client-side and server-side
  • Success/error messages display correctly
  • Email sending works (if applicable)

Browser/Device Testing

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)
  • Mobile iOS
  • Mobile Android
  • Tablet

Performance Testing

  • Page load time acceptable
  • No JavaScript errors in console
  • No CSS conflicts with theme
  • Caching works properly
  • AJAX requests complete quickly

Security Testing

  • All outputs escaped properly
  • SQL injection prevention
  • XSS prevention
  • CSRF protection
  • File upload validation
  • Input validation/sanitization

Accessibility Testing

  • Keyboard navigation works
  • Screen reader friendly
  • Proper ARIA labels
  • Focus indicators visible
  • Color contrast sufficient

Reference: ItTools Module Examples

Real working examples from this codebase:

  1. ItTools_QuoteForm (app/code/ItTools/QuoteForm/)

- Modal popup widget - Form with file uploads - Email functionality - AJAX submission

  1. ItTools_CategorySearch (app/code/ItTools/CategorySearch/)

- Simple search widget - Extends existing functionality - Custom placeholder text parameter

Use these as reference implementations.

Additional Resources

Magento DevDocs

Code Examples

  • Magento core widgets: vendor/magento/module-widget/
  • Magento CMS widgets: vendor/magento/module-cms/Block/Widget/
  • Catalog widgets: vendor/magento/module-catalog/Block/Widget/

Version Compatibility

This skill is compatible with:

  • Magento Open Source 2.3.x - 2.4.x
  • Adobe Commerce 2.3.x - 2.4.x
  • Mage-OS (Magento fork)

Not compatible with:

  • Hyvä themes (use hyva-tailwind-integration skill instead)
  • Magento 1.x

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

34.28%
按下载量换算23

Claude

28.88%
按下载量换算19

Cursor

18.84%
按下载量换算12

Gemini CLI

8.51%
按下载量换算6

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills