Frozen Header & Footer
Easy Email Pro provides two approaches to create fixed header and footer elements to meet different business requirements:
- Fully Frozen Approach: Using
headerElementandfooterElementconfiguration, users cannot modify or delete them at all - Partially Editable Approach: Using blocks with
PAGE_HEADERandPAGE_FOOTERcategory, fixed at the top and bottom, cannot be dragged or deleted, but allows partial content modification (such as text and background color)
Approach 1: Fully Frozen Header and Footer
Suitable for scenarios that require complete content locking, such as watermarks that can only be removed after user subscription, or required subscription links that must be retained.
Configuration
Configure frozen blocks in your editor setup:
import { Element } from "easy-email-pro-core";
// Define header element
const headerElement: Element = {
type: "standard-section",
data: {},
attributes: {
"background-repeat": "no-repeat",
"background-color": "#4a90e2",
},
children: [
{
type: "standard-column",
attributes: {
width: "100%",
},
data: {},
children: [
{
name: "Text",
type: "standard-paragraph",
data: {},
attributes: {},
children: [
{
text: "FROZEN HEADER",
},
],
},
],
},
],
};
// Define footer element
const footerElement: Element = {
type: "standard-section",
data: {},
attributes: {
"background-repeat": "no-repeat",
"background-color": "#4a90e2",
},
children: [
{
type: "standard-column",
attributes: {
width: "100%",
},
data: {},
children: [
{
name: "Text",
type: "standard-paragraph",
data: {},
attributes: {},
children: [
{
text: "FROZEN FOOTER",
},
],
},
],
},
],
};
// Apply to editor
export default function MyEditor() {
const config = Retro.useCreateConfig({
// ... other config options
headerElement: headerElement,
footerElement: footerElement,
});
return (
<EmailEditorProvider {...config}>
<EditorHeader />
<Layout.Content>
<Retro.Layout />
</Layout.Content>
</EmailEditorProvider>
);
}
Server-side Usage
When generating the final email HTML on the server, include the frozen elements:
const emailHtml = EditorCore.toMJML({
element: pageElement as PageElement,
mode: "production",
mergetagsData: {},
headerElement: headerElement,
footerElement: footerElement,
});
Features
- Fully frozen, users cannot modify or delete
- Displayed in both edit and preview modes
- Must include the same elements when generating the final email HTML
- Supports all standard block attributes and styles
Approach 2: Partially Editable Page Header and Page Footer
Suitable for scenarios that require fixed positions but allow partial modifications, for example:
- Footer has required subscription links that must be retained, but allows modification of text and background color
- Header has a fixed navigation structure, but allows style adjustments
Default Implementation
Easy Email Pro has built-in basic PageHeader and PageFooter blocks that can be used directly in the editor. These blocks are configured by default with PAGE_HEADER and PAGE_FOOTER category, and will automatically apply restrictions for fixed position, drag prevention, and deletion prevention.
Using in Templates
In the email template JSON data, you can directly use blocks of type page-header and page-footer:
{
"type": "page",
"children": [
{
"type": "page-header",
"data": {
"content": [
{
"type": "standard-section",
"children": [
{
"type": "standard-column",
"children": [
{
"type": "standard-paragraph",
"children": [{ "text": "This is fixed header content" }]
}
]
}
]
}
],
"editable": true
},
"attributes": {
"background-color": "#f0f0f0",
"padding-top": "20px",
"padding-bottom": "20px"
}
},
{
"type": "standard-section",
"children": [
// Main email content
]
},
{
"type": "page-footer",
"data": {
"content": [
{
"type": "standard-section",
"children": [
{
"type": "standard-column",
"children": [
{
"type": "standard-paragraph",
"children": [{ "text": "This is fixed footer content" }]
}
]
}
]
}
],
"editable": true
},
"attributes": {
"background-color": "#f0f0f0",
"padding-top": "20px",
"padding-bottom": "20px"
}
}
]
}
Editor Behavior
- These blocks will automatically appear in the header or footer area of the editor
- Users cannot drag to move their positions
- Operation tools such as delete buttons will be hidden
- Editable Property: The
editableproperty indatacontrols whether the content within the block can be edited- When
editable: true: Users can trigger editing interactions and modify content within the blocks (such as text, background color, and other style attributes) - When
editable: false: Content editing is disabled, and users cannot modify the content within the blocks
- When
- Child elements within the blocks can be edited normally when
editable: true
Default Configuration Panel
The editor provides default configuration panels (ConfigurationPanel) for PageHeader and PageFooter to help users quickly configure common properties:
PageHeader Default Configuration Items:
- Logo image URL
- Logo width
- Background color
PageFooter Default Configuration Items:
- Text content
- Background color
These configuration panels are implemented using the Schema approach and will automatically map to the corresponding data structure paths.
Custom Configuration Panel
If the default configuration panel cannot meet your needs, you can customize the configuration panel by overriding BlockSchemasMap or ConfigPanelsMap.
Method 1: Using Schema Approach (Recommended)
The Schema approach is simpler and suitable for most scenarios. Define the configuration panel by overriding BlockSchemasMap:
import { BlockSchemasMap } from "easy-email-pro-theme";
import { ElementType } from "easy-email-pro-core";
import { BlockSchema } from "@easy-email-pro-theme/typings";
import { t } from "easy-email-pro-core";
// Custom PageHeader configuration panel Schema
const CustomPageHeaderSchema: BlockSchema = [
{
type: "CollapseGroup",
name: "0",
header: t("Header Settings"),
defaultActive: true,
children: [
{
type: "ImageUrl",
name: "data.content.0.children.0.children.0.attributes.src",
label: t("Logo"),
},
{
type: "PixelField",
name: "data.content.0.children.0.children.0.attributes.width",
label: t("Logo Width"),
},
{
type: "BackgroundColor",
name: "attributes.background-color",
label: t("Background color"),
},
// Add more custom configuration items...
],
},
];
// Custom PageFooter configuration panel Schema
const CustomPageFooterSchema: BlockSchema = [
{
type: "CollapseGroup",
name: "0",
header: t("Footer Settings"),
defaultActive: true,
children: [
{
type: "TextField",
name: "data.content.0.children.0.children.0.children.0.text",
label: t("Footer Text"),
},
{
type: "BackgroundColor",
name: "attributes.background-color",
label: t("Background color"),
},
{
type: "TextField",
name: "attributes.padding-top",
label: t("Padding Top"),
},
{
type: "TextField",
name: "attributes.padding-bottom",
label: t("Padding Bottom"),
},
// Add more custom configuration items...
],
},
];
// Override the default configuration panel
BlockSchemasMap[ElementType.PAGE_HEADER] = CustomPageHeaderSchema;
BlockSchemasMap[ElementType.PAGE_FOOTER] = CustomPageFooterSchema;
Method 2: Using React Component Approach
If you need more complex interaction logic, you can use the React component approach. Define the configuration panel by overriding ConfigPanelsMap:
import { ConfigPanelsMap } from "easy-email-pro-theme";
import { ElementType } from "easy-email-pro-core";
import { Path } from "slate";
import React from "react";
import {
AttributesPanelWrapper,
CollapseWrapper,
AttributeField,
} from "easy-email-pro-theme";
import { useSelectedNode } from "easy-email-pro-editor";
import { t } from "easy-email-pro-core";
import { Collapse } from "@arco-design/web-react";
// Custom PageHeader configuration panel component
const CustomPageHeaderPanel = ({ nodePath }: { nodePath: Path }) => {
const { selectedNode } = useSelectedNode();
if (!selectedNode) return null;
return (
<AttributesPanelWrapper>
<CollapseWrapper defaultActiveKey={["0", "1"]}>
<Collapse.Item name="0" header={t("Logo Settings")}>
<AttributeField.ImageUrl
label={t("Logo URL")}
name="data.content.0.children.0.children.0.attributes.src"
path={nodePath}
/>
<AttributeField.PixelField
label={t("Logo Width")}
name="data.content.0.children.0.children.0.attributes.width"
path={nodePath}
/>
</Collapse.Item>
<Collapse.Item name="1" header={t("Style Settings")}>
<AttributeField.BackgroundColor
label={t("Background color")}
name="attributes.background-color"
path={nodePath}
/>
<AttributeField.TextField
label={t("Padding Top")}
name="attributes.padding-top"
path={nodePath}
/>
<AttributeField.TextField
label={t("Padding Bottom")}
name="attributes.padding-bottom"
path={nodePath}
/>
</Collapse.Item>
</CollapseWrapper>
</AttributesPanelWrapper>
);
};
// Custom PageFooter configuration panel component
const CustomPageFooterPanel = ({ nodePath }: { nodePath: Path }) => {
const { selectedNode } = useSelectedNode();
if (!selectedNode) return null;
return (
<AttributesPanelWrapper>
<CollapseWrapper defaultActiveKey={["0"]}>
<Collapse.Item name="0" header={t("Footer Content")}>
<AttributeField.TextField
label={t("Footer Text")}
name="data.content.0.children.0.children.0.children.0.text"
path={nodePath}
/>
<AttributeField.BackgroundColor
label={t("Background color")}
name="attributes.background-color"
path={nodePath}
/>
</Collapse.Item>
</CollapseWrapper>
</AttributesPanelWrapper>
);
};
// Override the default configuration panel
ConfigPanelsMap[ElementType.PAGE_HEADER] = CustomPageHeaderPanel;
ConfigPanelsMap[ElementType.PAGE_FOOTER] = CustomPageFooterPanel;
Comparison of Both Approaches
| Feature | Schema Approach | React Component Approach |
|---|---|---|
| Complexity | Simple, declarative | Complex, imperative |
| Flexibility | Suitable for standard config items | Suitable for complex interaction logic |
| Performance | Better | Slightly worse (requires component rendering) |
| Recommended | Most scenarios | Complex logic or custom UI |
Available Schema Field Types
The Schema approach supports multiple field types, commonly used ones include:
Basic Field Types:
TextField: Text input fieldNumberField: Number input fieldPixelField: Pixel value input (e.g., width, height)ColorPickerField: Color pickerSelectField: Dropdown selectionSwitchField: Toggle switchSliderField: Slider inputTextAreaField: Multi-line text inputImageUploaderField: Image uploadRichTextField: Rich text editor
Composite Field Types:
BackgroundColor: Background color pickerImageUrl: Image URL inputPadding: Padding settings (top, right, bottom, left)Border: Border settingsBorderRadius: Border radius settingsTypography: Font style settingsTextAlign: Text alignmentLink: Link settingsBackgroundImage: Background image settingsBackgroundGradient: Background gradient settings
Container Types:
CollapseGroup: Collapsible group containerResponsiveTabs: Responsive tabs (desktop/mobile)
For more field types, please refer to the type definitions in @easy-email-pro-theme/typings.
Priority Explanation
Configuration panel loading priority:
- Check
BlockSchemasMapfirst: If a Schema definition exists, use Schema rendering - Fallback to
ConfigPanelsMap: If no Schema exists, use React component rendering - If neither exists: No configuration panel is displayed
Therefore, if you override both BlockSchemasMap and ConfigPanelsMap, the system will prioritize using the Schema in BlockSchemasMap.
Complete Example
import { BlockSchemasMap, ConfigPanelsMap } from "easy-email-pro-theme";
import { ElementType } from "easy-email-pro-core";
import { Retro } from "easy-email-pro-theme";
import { EmailEditorProvider } from "easy-email-pro-editor";
// Method 1: Using Schema (Recommended)
import {
CustomPageHeaderSchema,
CustomPageFooterSchema,
} from "./custom-schemas";
BlockSchemasMap[ElementType.PAGE_HEADER] = CustomPageHeaderSchema;
BlockSchemasMap[ElementType.PAGE_FOOTER] = CustomPageFooterSchema;
// Or Method 2: Using React Component
// import { CustomPageHeaderPanel, CustomPageFooterPanel } from "./custom-panels";
// ConfigPanelsMap[ElementType.PAGE_HEADER] = CustomPageHeaderPanel;
// ConfigPanelsMap[ElementType.PAGE_FOOTER] = CustomPageFooterPanel;
export default function MyEditor() {
const config = Retro.useCreateConfig({
// ... other config
});
return (
<EmailEditorProvider {...config}>
<EditorHeader />
<Layout.Content>
<Retro.Layout />
</Layout.Content>
</EmailEditorProvider>
);
}
Features
- Fixed at top/bottom: Blocks using
PAGE_HEADERorPAGE_FOOTERcategory will automatically be fixed at the top or bottom of the email template - Cannot be dragged: These blocks cannot be moved by dragging
- Cannot be deleted: Operation tools such as delete buttons will be hidden, and users cannot delete these blocks
- Partially editable: Although they cannot be deleted or moved, partial content modification is allowed when
editable: true:- Modify text content
- Adjust background color
- Modify style attributes
- Edit child elements within the block
- Editable property: The
data.editableproperty determines whether editing interactions can be triggered. Only wheneditable: truecan users edit the content within the blocks
Implementation Principle
The system determines whether a block is PAGE_HEADER or PAGE_FOOTER by checking the block's category property:
// Defined in blockCategory.ts
export const ElementCategory = {
PAGE_HEADER: "page-header" as const,
PAGE_FOOTER: "page-footer" as const,
// ... other categories
};
The editor will automatically recognize these categories and apply corresponding restrictions:
- Dragging will be prevented
- Delete tools will be hidden
- Blocks will be fixed at their corresponding positions
Custom Extension
If the default PageHeader and PageFooter blocks cannot meet your needs, you can extend functionality by creating custom blocks (Custom Block).
Creating Custom Page Header
import { createBlock, ElementCategory, BasicType } from "easy-email-pro-core";
import { PageHeaderElement } from "easy-email-pro-core/typings";
export const CustomPageHeader = createBlock<PageHeaderElement>({
get name() {
return "Custom Header";
},
type: BasicType.PAGE_HEADER, // or use custom type
get category() {
return ElementCategory.PAGE_HEADER; // Key: Must use PAGE_HEADER category
},
defaultData: {
attributes: {
"background-color": "#ffffff",
"padding-top": "20px",
"padding-bottom": "20px",
},
data: {
content: [],
editable: true, // Set to true to enable editing interactions
},
},
// Custom rendering logic
render(params) {
// Your custom rendering implementation
return <YourCustomHeaderComponent {...params} />;
},
// Custom configuration panel
panel: [
// Your custom configuration items
],
});
Creating Custom Page Footer
import { createBlock, ElementCategory, BasicType } from "easy-email-pro-core";
import { PageFooterElement } from "easy-email-pro-core/typings";
export const CustomPageFooter = createBlock<PageFooterElement>({
get name() {
return "Custom Footer";
},
type: BasicType.PAGE_FOOTER, // or use custom type
get category() {
return ElementCategory.PAGE_FOOTER; // Key: Must use PAGE_FOOTER category
},
defaultData: {
attributes: {
"background-color": "#f5f5f5",
"padding-top": "20px",
"padding-bottom": "20px",
},
data: {
content: [],
editable: true, // Set to true to enable editing interactions
},
},
// Custom rendering logic
render(params) {
// Your custom rendering implementation
return <YourCustomFooterComponent {...params} />;
},
// Custom configuration panel
panel: [
// Your custom configuration items
],
});
Registering Custom Blocks
After creating custom blocks, you need to register them in the editor:
import { BlockManager } from "easy-email-pro-core";
import { CustomPageHeader, CustomPageFooter } from "./custom-blocks";
// Register custom blocks
BlockManager.registerBlocks([
CustomPageHeader,
CustomPageFooter,
// ... other custom blocks
]);
// Then use in editor configuration
export default function MyEditor() {
const config = Retro.useCreateConfig({
// ... other config
});
return (
<EmailEditorProvider {...config}>
<EditorHeader />
<Layout.Content>
<Retro.Layout />
</Layout.Content>
</EmailEditorProvider>
);
}
Notes for Custom Blocks
- Category must be correct: Custom blocks must use
ElementCategory.PAGE_HEADERorElementCategory.PAGE_FOOTERso that the editor can correctly identify and apply restrictions - Type can be customized: Although the category must be fixed, the
typecan use custom values, allowing you to create multiple header or footer blocks with different styles - Data structure: It is recommended to follow the data structure of
PageHeaderElementorPageFooterElementto ensure compatibility with the system - Editability control: The
data.editableproperty controls whether editing interactions can be triggered:editable: true: Enables editing interactions, allowing users to modify content within the blockeditable: false: Disables editing interactions, preventing users from modifying content within the block
Use Cases
Branding Requirements
- Company logo in header
- Legal disclaimers in footer
- Copyright notices
Subscription Features
- Display watermarks for free users (Fully Frozen Approach)
- Show premium features for paid users
- Required subscription links that must be retained (Partially Editable Approach)
Compliance Requirements
- Required legal text
- Unsubscribe links (Partially editable, allows style modification)
- Contact information
Approach Comparison
| Feature | Fully Frozen Approach (headerElement/footerElement) | Partially Editable Approach (PAGE_HEADER/PAGE_FOOTER) |
|---|---|---|
| Fixed Position | ✅ | ✅ |
| Draggable | ❌ | ❌ |
| Deletable | ❌ | ❌ |
| Editable Content | ❌ | ✅ (partial) |
| Editable Style | ❌ | ✅ |
| Use Cases | Fully locked watermarks, logos | Required but adjustable links, text |
Important Notes
- Fully Frozen Approach: Elements cannot be modified or deleted at all, suitable for scenarios requiring strict control
- Partially Editable Approach: Fixed at top/bottom, cannot be dragged or deleted, but allows partial content and style modification
- Elements from both approaches will be displayed in both edit and preview modes
- The same elements must be included when generating the final email HTML
- Both approaches support all standard block attributes and styles
For more examples and advanced usage, check our demo application.