HarmonyOS NEXT Responsive Layout: Semantic Breakpoints, Slot Architecture & State Preservation
This article presents a practical approach to building responsive layouts in HarmonyOS NEXT by defining semantic window breakpoints (narrow/medium/wide), centralizing breakpoint logic in a dedicated store, separating layout concerns into a PageShell component, and preserving UI state like selection and scroll position across layout changes.
From Device Detection to Window Semantics
The author describes moving away from device-based responsive design (phone/tablet/foldable) toward responding to actual window size and orientation. The core principle: width changes affect layout, not data semantics .
For a message center page, three semantic layout modes are defined: narrow: single-column content organization medium: list and detail can sit side-by-side wide: filter, list, and detail form three stable panels
These modes describe page semantics , not device models. The same device in portrait, landscape, split-screen, or free-form window yields different available space — the layout should respond to window dimensions, not device names.
Slot-Based Architecture: Separating Layout from Responsibility
The page is decomposed into four stable slots:
Navigation / top action bar
Filter or auxiliary area
Main list area
Detail or preview area
Instead of letting components change identity across breakpoints (e.g., detail becomes a drawer then a sidebar), the author keeps data and state as single sources of truth . Only the slots rearrange. This prevents selection loss, scroll reset, and cache invalidation when layouts shift.
Centralized Breakpoint Store
Using HarmonyOS NEXT's mediaquery API, a BreakpointStore encapsulates all breakpoint logic:
import { mediaquery } from '@kit.ArkUI'
export type LayoutMode = 'narrow' | 'medium' | 'wide'
export class BreakpointStore {
private uiContext?: UIContext
private listeners: mediaquery.MediaQueryListener[] = []
mode: LayoutMode = 'narrow'
isLandscape: boolean = false
attach(uiContext: UIContext) {
this.uiContext = uiContext
const mq = uiContext.getMediaQuery()
const narrow = mq.matchMediaSync('(width < 600vp)')
const medium = mq.matchMediaSync('(width >= 600vp) and (width < 840vp)')
const wide = mq.matchMediaSync('(width >= 840vp)')
const landscape = mq.matchMediaSync('(orientation: landscape)')
const update = () => {
if (wide.matches) {
this.mode = 'wide'
} else if (medium.matches) {
this.mode = 'medium'
} else {
this.mode = 'narrow'
}
this.isLandscape = landscape.matches
}
;[narrow, medium, wide, landscape].forEach((item) => {
item.on('change', update)
this.listeners.push(item)
})
update()
}
detach() {
this.listeners.forEach((item) => item.off('change'))
this.listeners = []
}
}Key points:
No raw width checks scattered across components
Single source emits readable semantics: narrow / medium / wide Pages consume semantics, not raw dimensions
Thresholds (600/840vp) are adjustable with design
PageShell: Layout-Only Component
The page layer ( PageShell) only decides how slots arrange . It holds no data fetching or business logic:
@Component
export struct MessageCenterPage {
@State store: BreakpointStore = new BreakpointStore()
@State vm: MessageCenterViewModel = new MessageCenterViewModel()
aboutToAppear() {
this.store.attach(this.getUIContext())
}
aboutToDisappear() {
this.store.detach()
}
build() {
Column() {
this.HeaderBar()
if (this.store.mode === 'narrow') {
this.NarrowLayout()
} else if (this.store.mode === 'medium') {
this.MediumLayout()
} else {
this.WideLayout()
}
}
.width('100%')
.height('100%')
}
@Builder
NarrowLayout() {
Column({ space: 12 }) {
this.FilterEntry()
this.MessageList()
this.DetailPreviewCard()
}
}
@Builder
MediumLayout() {
Row({ space: 12 }) {
this.MessageList()
this.DetailPanel()
}
}
@Builder
WideLayout() {
Row({ space: 16 }) {
this.FilterPanel()
this.MessageList()
this.DetailPanel()
}
}
}Benefits: PageShell only cares about arrangement ViewModel only cares about data MessageList, DetailPanel only care about presentation
Preserving State Across Layout Changes
Three invariants are deliberately guarded:
1. Selected Item Stays Independent
The detail panel is merely a display slot; it must not control whether a selection exists. Selection state lives in the shared store, not in the detail component.
2. Scroll Position Is Restorable
For feeds (messages, products, documents), users notice when scroll jumps to top after a layout shift. Scroll offset should be saved and restored.
3. Filter Conditions Remain Unified
Filters must not be duplicated per breakpoint. One store holds filter state; layout changes only affect entry point visibility.
Leverage Container Capabilities Over Conditional Rendering
Instead of hiding/showing components via conditionals, use HarmonyOS layout primitives progressively:
Page skeleton: Row / Column Complex column ratios: GridRow / GridCol Explicit sidebar + content: sidebar containers
The rule: layout rules exist in one place . Changing from two-column to three-column, or moving detail from sidebar to drawer, becomes a localized change.
Orientation ≠ Layout Mode
Early mistake: equating landscape with "large screen." Small phones in landscape gain height but not enough width for stable two-column layouts.
Now separated: orientation: describes direction (portrait/landscape) layout mode: describes available space (narrow/medium/wide) BreakpointStore outputs both mode and isLandscape, but layout decisions prioritize mode.
Ideal Page Types for This Approach
Works best for pages with inherent hierarchical content:
Chat: conversation list + detail/profile
Document: TOC + body + annotations/properties
Admin dashboard: filters + list + detail
Settings: grouped navigation + content panel
These pages have natural layers; responsive design becomes reflowing existing layers , not redesigning UI.
Post-Implementation Checklist
Does the selected item persist when width changes?
Does list scroll position avoid meaningless jumps?
Do filter conditions remain a single source?
Are child components free of excessive layout logic?
If these four hold, the page is functionally solid; visual polish (spacing, grid, whitespace) comes after.
Three-Step Starting Guide
Extract breakpoint semantics into one layer — no raw width reads scattered
Define slots; let layout changes affect position only, never data
Apply visual refinement last — don't start by painting three separate UIs
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
51CTO HarmonyOS Developer Community
The HarmonyOS Developer Community is a learning-oriented community for developers to learn, communicate, ask questions, and share.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
