Purpose: Technical documentation of viewport breakpoints, detection logic, and fallback behavior
Breakpoint Definitions
Client-Side (Browser)
Breakpoints are determined by window.innerWidth using Gutenberg /
@wordpress/compose thresholds (same as the block editor device preview):
import { resolveViewportFromWidth } from './viewport-breakpoints';
function getViewportFromWidth(width) {
return resolveViewportFromWidth(width);
}
Breakpoints (aligned with @wordpress/compose useViewportMatch):
- Mobile:
width < 480px - Tablet:
480px ≤ width < 782px - Desktop:
width ≥ 782px
Editor preview canvas widths are 479px (Mobile) and 781px (Tablet) — one
pixel below each threshold so the preview matches these ranges.
Server-Side (PHP)
Breakpoints are determined by PRC\Platform\get_current_device():
$device_type = \PRC\Platform\get_current_device();
// Returns: 'desktop' | 'tablet' | 'mobile'
The server-side function uses Jetpack User-Agent detection (is_phone /
is_tablet), not pixel breakpoints. Client-side resize uses the Gutenberg
thresholds above so live behavior matches editor device preview overrides.
Editor Preview
The WordPress block editor provides device preview modes that match these breakpoints:
- Desktop: Default view, full editor width
- Tablet: Simulated tablet width (~768px)
- Mobile: Simulated mobile width (~375px)
Viewport Detection Flow
Editor (Client-Side)
- User selects device preview in editor toolbar
- WordPress Redux store updates
deviceType useDeviceType()hook reads from store- Chart re-renders with viewport-specific attributes
File: src/chart/edit/use-viewport-attributes.js
Frontend (Client-Side)
- Page loads with server-rendered chart
watchForResizehandler monitorswindow.innerWidth- When breakpoint changes, navigates to new URL with
cb_viewportquery param - Server re-renders chart with correct viewport
- Client rehydrates with new server state
File: src/chart/view.js
Server-Side Rendering
- Request arrives (with optional
cb_viewportquery param) get_current_device()determines device typemerge_viewport_attributes()merges viewport overrides- Chart renders with merged attributes
- Server state includes
currentViewportfor client hydration
File: src/chart/class-chart.php
Fallback Behavior
Attribute Resolution Order
When reading an attribute value:
- Check viewport override (if not desktop):
if ( deviceType !== 'desktop' && attributes[deviceType]?.[group]?.[key] !== undefined ) { return attributes[deviceType][group][key]; } - Fall back to desktop default:
return attributes[group]?.[key];
Examples
Example 1: Mobile Override Exists
{
"labels": { "fontSize": 12 },
"mobile": { "labels": { "fontSize": 10 } }
}
Result:
- Desktop:
12px(fromlabels.fontSize) - Tablet:
12px(fromlabels.fontSize, no tablet override) - Mobile:
10px(frommobile.labels.fontSize)
Example 2: Tablet Override Exists
{
"labels": { "fontSize": 12 },
"tablet": { "labels": { "fontSize": 11 } }
}
Result:
- Desktop:
12px(fromlabels.fontSize) - Tablet:
11px(fromtablet.labels.fontSize) - Mobile:
12px(fromlabels.fontSize, no mobile override)
Example 3: Both Overrides Exist
{
"labels": { "fontSize": 12 },
"tablet": { "labels": { "fontSize": 11 } },
"mobile": { "labels": { "fontSize": 10 } }
}
Result:
- Desktop:
12px(fromlabels.fontSize) - Tablet:
11px(fromtablet.labels.fontSize) - Mobile:
10px(frommobile.labels.fontSize)
Example 4: Partial Override
{
"labels": { "fontSize": 12, "color": "inherit" },
"mobile": { "labels": { "fontSize": 10 } }
}
Result (Mobile):
fontSize:10px(frommobile.labels.fontSize)color:"inherit"(fromlabels.color, no mobile override)
Content Attributes (No Fallback)
Important: Content attributes (io, dataRender, colors, divergingBar) are NOT viewport-aware. They always use the base attribute value:
// Content attributes - direct access, no viewport override
const io = attributes.io || {};
const dataRender = attributes.dataRender || {};
Why: These represent WHAT data to show, which should be consistent across all viewports.
Edge Cases
Case 1: Rapid Viewport Switching
Behavior: Debounced resize handler (250ms) prevents excessive re-renders.
Implementation: src/chart/view.js – watchForResize uses debounce.
Case 2: Viewport Override Deleted
Behavior: Chart reverts to desktop default immediately.
Implementation: When override value matches desktop value, override is effectively removed.
Case 3: Missing Viewport Override
Behavior: Falls back to desktop default seamlessly.
Implementation: getCurrentValue() checks for override existence before using.
Case 4: Invalid Device Type
Behavior: Defaults to 'desktop' and uses base attributes.
Implementation:
const deviceType = type ? type.toLowerCase() : 'desktop';
Case 5: Server-Side Query Parameter Override
Behavior: cb_viewport query parameter can force a specific viewport for testing.
Implementation: class-chart.php checks $_GET['cb_viewport'] before calling get_current_device().
Performance Considerations
Merge Performance
- Desktop: No merge needed (returns base attributes directly)
- Tablet/Mobile: Shallow merge of attribute groups (~0.01ms overhead)
Target: <10% performance degradation with viewport overrides.
Re-render Performance
- Device switching: <100ms target
- Debounced resize: 250ms delay prevents excessive renders
Testing Breakpoints
Manual Testing
- Desktop: Open browser at ≥782px width
- Tablet: Resize browser to 480–781px width
- Mobile: Resize browser to <480px width
Automated Testing
Use browser dev tools or testing frameworks to simulate viewport sizes:
// Set viewport width (Gutenberg breakpoints)
window.innerWidth = 375; // Mobile (< 480)
window.innerWidth = 640; // Tablet (480–781)
window.innerWidth = 900; // Desktop (≥ 782)
Server-Side Testing
Use query parameter to force viewport:
https://example.com/chart-page/?cb_viewport=mobile
https://example.com/chart-page/?cb_viewport=tablet
https://example.com/chart-page/?cb_viewport=desktop
Migration Notes
From v1 Charts
- v1 charts have no viewport overrides
- All viewports use the same (v1) attributes
- Migration to v2 preserves this behavior (no viewport overrides created)
Adding Viewport Overrides
- Viewport overrides are opt-in
- Charts without overrides work identically to before
- Overrides only created when explicitly set in device preview mode
Related Documentation
- Viewport attributes – Architecture and implementation details
- Viewport usage guide – Editor usage guide
- Architecture – How viewport attributes fit into the wider block architecture
Last Updated: 2026-01-12 Version: Chart Builder v3.3.0+