Blocks Layer - UI Components
OverviewDirect link to Overview
For implementation details of the Blocks Layer in the standards-sdk, see the HCS-12 SDK Guide documentation.
The Blocks Layer implements a decentralized, composable UI component system based on WordPress's Gutenberg block format. Blocks are stored as immutable assets via HCS-1, with their definitions and templates stored separately. Assemblies reference blocks directly by their HCS-1 topic IDs.
Block ArchitectureDirect link to Block Architecture
Gutenberg FoundationDirect link to Gutenberg Foundation
HashLinks adopts the Gutenberg block format for its proven flexibility and extensive ecosystem. Key benefits include:
- Composability: Blocks can contain other blocks, enabling complex layouts
- Portability: Standardized format works across different implementations
- Extensibility: Rich attribute system for customization
- Accessibility: Built-in support for screen readers and keyboard navigation
Block Storage StructureDirect link to Block Storage Structure
Blocks consist of two parts stored via HCS-1:
- Block Template (HTML): The rendering template with placeholders
- Block Definition (JSON): The Gutenberg block metadata including template reference
The template is stored first, then referenced in the block definition:
{
"apiVersion": 3,
"name": "hashlink/token-transfer",
"title": "Token Transfer",
"category": "hashlink/actions",
"icon": "money",
"description": "Transfer tokens between accounts",
"keywords": ["token", "transfer", "payment"],
"template_t_id": "0.0.123456",
"attributes": {
"tokenId": {
"type": "string",
"default": ""
},
"amount": {
"type": "number",
"default": 1
}
},
"supports": {
"align": ["wide", "full"],
"anchor": true
}
}
This approach keeps the template separate (avoiding JSON escaping issues) while making blocks self-contained.
Gutenberg CompatibilityDirect link to Gutenberg Compatibility
HashLinks blocks are designed to be fully compatible with the WordPress Gutenberg editor, enabling:
- Drag-and-Drop Editing: Users can visually arrange blocks using any Gutenberg-compatible editor
- Visual Block Selection: Browse and insert blocks from a visual library
- Live Preview: See changes in real-time as blocks are configured
- Nested Composition: Create complex layouts with container blocks
- Responsive Design: Blocks automatically adapt to different screen sizes
Using Gutenberg EditorsDirect link to Using Gutenberg Editors
HashLinks blocks can be created and edited using any Gutenberg-compatible editor:
- Open Editor: Use WordPress, Gutenberg standalone, or any compatible block editor
- Insert Blocks: Browse the HashLink category in the block inserter
- Configure: Use the block settings panel to configure attributes
- Preview: See real-time preview of your HashLink experience
- Export: Save the block structure as JSON for registration
Block Creation ProcessDirect link to Block Creation Process
Blocks are created by storing their template first, then their definition:
Step 1: Store Block TemplateDirect link to Step 1: Store Block Template
Store the HTML template via HCS-1:
<!-- token-transfer-template.html -->
<div class="hashlink-block token-transfer" data-block-id="{{blockId}}">
<h3>{{attributes.title}}</h3>
<form data-action="{{actionTopicId}}" data-params='{"operation": "transfer"}'>
<label>
Token ID:
<input type="text" name="tokenId" value="{{attributes.tokenId}}" />
</label>
<label>
Recipient:
<input type="text" name="recipient" value="{{attributes.recipient}}" />
</label>
<label>
Amount:
<input type="number" name="amount" value="{{attributes.amount}}" />
</label>
<button type="submit">Transfer</button>
</form>
{{#if actionResults.transfer}}
<div class="result success">
Transfer complete! TX: {{actionResults.transfer.transactionId}}
</div>
{{/if}}
</div>
Store this template via HCS-1. The returned topic ID (e.g., "0.0.123456") will be referenced in the block definition.
Step 2: Store Block DefinitionDirect link to Step 2: Store Block Definition
Store the block definition with template reference:
Block definition JSON:
{
"apiVersion": 3,
"name": "hashlink/token-transfer",
"title": "Token Transfer Form",
"category": "hashlink/actions",
"description": "Transfer tokens with a customizable form",
"keywords": ["token", "transfer", "hedera"],
"template_t_id": "0.0.123456",
"attributes": {
"tokenId": {
"type": "string",
"default": ""
},
"amount": {
"type": "number",
"default": 1
},
"recipient": {
"type": "string",
"default": ""
}
},
"supports": {
"align": ["wide", "full"],
"anchor": true
}
}
Store this JSON via HCS-1. The returned topic ID (e.g., "0.0.123457") is used to reference the block.
Step 3: Use in AssemblyDirect link to Step 3: Use in Assembly
Reference the block in an assembly by its definition topic ID:
{
"p": "hcs-12",
"op": "add-block",
"block_t_id": "0.0.123457",
"actions": {
"transfer": "0.0.789012"
},
"attributes": {
"tokenId": "0.0.456789",
"amount": 100
}
}
Template SystemDirect link to Template System
HTML TemplatesDirect link to HTML Templates
Block templates use standard HTML with Handlebars-style placeholders and include resources via HCS-3:
<link
rel="stylesheet"
data-src="hcs://1/0.0.111111"
data-script-id="token-transfer-styles"
/>
<div
class="hashlink-block {{attributes.className}}"
data-block-id="{{blockId}}"
>
<h3>{{attributes.title}}</h3>
{{#if attributes.showDescription}}
<p>{{attributes.description}}</p>
{{/if}}
<form data-action="{{actions.0}}">
<label>
Token ID:
<input type="text" name="tokenId" value="{{attributes.tokenId}}" />
</label>
<label>
Amount:
<input type="number" name="amount" value="{{attributes.defaultAmount}}" />
</label>
{{#if attributes.showAdvanced}}
<div class="advanced-options">
<!-- Additional fields -->
</div>
{{/if}}
<button type="submit">Transfer</button>
</form>
{{#if action.result}}
<div class="result">{{ action.result.message }}</div>
{{/if}}
</div>
<!-- Block-specific JavaScript -->
<script
data-src="hcs://1/0.0.222222"
data-script-id="token-transfer-logic"
></script>
Resource Loading with HCS-3Direct link to Resource Loading with HCS-3
Resources are loaded using the HCS-3 protocol:
<link
rel="stylesheet"
data-src="hcs://1/0.0.123456"
data-script-id="block-styles"
/>
<script
data-src="hcs://3/0.0.789012"
data-dependencies="['jquery', 'react']"
></script>
<img
data-src="hcs://1/{{ attributes.imageTopicId }}"
alt="{{ attributes.imageAlt }}"
loading="lazy"
/>
Action BindingDirect link to Action Binding
Blocks bind to actions using topic IDs directly. This ensures deterministic behavior and clear dependencies.
Declaring Actions in AssemblyDirect link to Declaring Actions in Assembly
When adding a block to an assembly, provide a map of action names to topic IDs:
{
"p": "hcs-12",
"op": "add-block",
"block_t_id": "0.0.123456",
"actions": {
"primary": "0.0.789012"
},
"attributes": {
"title": "Transfer HBAR"
}
}
Using Actions in TemplatesDirect link to Using Actions in Templates
Templates receive actions as a map of names to topic IDs:
For blocks with multiple actions:
{
"p": "hcs-12",
"op": "add-block",
"block_t_id": "0.0.123456",
"actions": {
"transfer": "0.0.789012",
"validate": "0.0.789013",
"getBalance": "0.0.789014"
}
}
In the template:
Action Execution FlowDirect link to Action Execution Flow
- User Interaction: User clicks button or submits form
- Data Collection: Form data or data-params are collected
- Action Loading: The WASM module is loaded from the action topic ID
- WASM Execution: The action's POST method is called with:
action: The method name from the action definitionparams: Collected form data or specified parametersnetwork: Current Hedera networkhashLinkMemo: Assembly identifier
- Result Handling: The WASM response updates the block state
Event HandlersDirect link to Event Handlers
Blocks can specify different events for actions:
Passing ParametersDirect link to Passing Parameters
Parameters can be passed to actions in several ways:
Action ResultsDirect link to Action Results
Action results are available for template updates:
Nested Blocks (Recursive Composition)Direct link to Nested Blocks (Recursive Composition)
HashLinks supports nested blocks through template-based composition. Container blocks can include other blocks directly in their templates, enabling flexible layouts without requiring explicit child references.
Template-Based CompositionDirect link to Template-Based Composition
Blocks compose other blocks using HashLink references directly in templates:
Dynamic Block ListsDirect link to Dynamic Block Lists
For dynamic lists of blocks, use template helpers:
Child Block ContextDirect link to Child Block Context
Child blocks inherit context from their parent:
Recursive Block ExampleDirect link to Recursive Block Example
A dashboard composing nested sections through its template:
The Stats Container block template can then include its own nested blocks:
State Management for Nested BlocksDirect link to State Management for Nested Blocks
Each block maintains its own state, with child states accessible to parents:
// Parent block state
{
"attributes": {
"title": "Dashboard",
"theme": "dark"
},
"actionResults": {},
"childStates": {
"0.0.100001": {
"attributes": { "value": 1000 },
"actionResults": {}
},
"0.0.100002": {
"attributes": { "status": "ready" },
"actionResults": {}
}
}
}
Event BubblingDirect link to Event Bubbling
Events from child blocks can bubble up to parents:
Block PatternsDirect link to Block Patterns
Pre-configured block arrangements can be registered as patterns:
{
"p": "hcs-12",
"op": "pattern",
"name": "defi-dashboard",
"title": "DeFi Dashboard",
"description": "Complete DeFi dashboard layout",
"categories": ["hashlink/patterns"],
"content": {
"name": "hashlink/container",
"attributes": {
"layout": "grid",
"columns": 2
},
"innerBlocks": [
{
"name": "hashlink/balance-display",
"attributes": {
"tokenId": "0.0.123456"
}
},
{
"name": "hashlink/token-transfer-form",
"attributes": {
"tokenId": "0.0.123456"
}
}
]
}
}
Security ConsiderationsDirect link to Security Considerations
Template SanitizationDirect link to Template Sanitization
All HTML templates MUST be sanitized:
- Remove script tags (use HCS-3 for scripts)
- Sanitize event handlers
- Validate URLs and links
- Escape user-provided content
Action ValidationDirect link to Action Validation
Blocks MUST validate action bindings:
- Verify action exists in registry
- Check if block is allowed to use the action
- Validate that required actions are available
Resource VerificationDirect link to Resource Verification
All resources loaded via HCS-3 MUST be verified:
- Check resource hashes
- Validate MIME types
- Enforce size limits
Example: NFT Gallery BlockDirect link to Example: NFT Gallery Block
Step 1: Store Block DefinitionDirect link to Step 1: Store Block Definition
Store the Gutenberg block definition via HCS-1:
First, store the template HTML via HCS-1. This returns topic ID: 0.0.456788
Step 2: Store Block DefinitionDirect link to Step 2: Store Block Definition
Block definition JSON:
{
"apiVersion": 3,
"name": "hashlink/nft-gallery",
"title": "NFT Gallery",
"category": "hashlink/media",
"description": "Display NFTs in a responsive gallery",
"template_t_id": "0.0.456788",
"attributes": {
"collectionId": {
"type": "string",
"default": ""
},
"columns": {
"type": "number",
"default": 3
},
"showPrice": {
"type": "boolean",
"default": true
}
},
"supports": {
"align": ["wide", "full"],
"spacing": {
"margin": true,
"padding": true
}
}
}
Store this JSON via HCS-1. This returns topic ID: 0.0.456789
Template HTML:
<div class="nft-gallery" data-columns="{{attributes.columns}}">
{{#each nfts}}
<div class="nft-item">
<img src="{{this.image}}" alt="{{this.name}}" />
<h4>{{this.name}}</h4>
{{#if ../attributes.showPrice}}
<p class="price">{{this.price}} ℏ</p>
{{/if}}
<div class="actions">
<button data-action-click="{{actions.viewDetails}}" data-nft-id="{{this.id}}">
View
</button>
<button data-action-click="{{actions.purchase}}" data-nft-id="{{this.id}}">
Buy Now
</button>
</div>
</div>
{{/each}}
</div>
Step 3: Use in AssemblyDirect link to Step 3: Use in Assembly
Add the block to an assembly with action bindings:
{
"p": "hcs-12",
"op": "add-block",
"block_t_id": "0.0.456789",
"actions": {
"viewDetails": "0.0.789012",
"purchase": "0.0.789013"
},
"attributes": {
"collectionId": "0.0.123456",
"columns": 4,
"showPrice": true
}
}
Best PracticesDirect link to Best Practices
- Follow Gutenberg Standards: Use standard Gutenberg block structure
- Keep Templates Simple: Complex logic belongs in actions, not templates
- Use Semantic HTML: Ensure accessibility
- Responsive Design: Test on all screen sizes
- Version Carefully: Blocks are immutable once registered
- Document Attributes: Clear descriptions for all block settings
- Action Binding:
- Use descriptive action aliases that match their purpose
- Validate parameters before sending to actions
- Handle both success and error states from actions
- Consider loading states for async action execution
- Use data-params for static values, forms for user input