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.

51CTO HarmonyOS Developer Community
51CTO HarmonyOS Developer Community
51CTO HarmonyOS Developer Community
HarmonyOS NEXT Responsive Layout: Semantic Breakpoints, Slot Architecture & State Preservation

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

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

frontend developmentState ManagementArkUIresponsive designbreakpointssemantic layoutHarmonyOS NEXTlayout architecture
51CTO HarmonyOS Developer Community
Written by

51CTO HarmonyOS Developer Community

The HarmonyOS Developer Community is a learning-oriented community for developers to learn, communicate, ask questions, and share.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.