This article breaks down a complete HarmonyOS 6 settings page implementation. It focuses on volume control, voice prompts, difficulty selection, side button configuration, and data clearing, while addressing scattered settings, hard-to-sync state, and complex dark mode adaptation. Keywords: HarmonyOS 6, ArkTS, settings page.
The technical specification snapshot provides a quick overview
| Parameter | Description |
|---|---|
| Development Language | ArkTS |
| UI Paradigm | Declarative UI |
| Target Platform | HarmonyOS 6 |
| Core Components | Slider, Toggle, Button, Row, Column |
| State Mechanism | @State + Observer Pattern |
| Data Persistence | SettingsManager + storageService |
| Theme Adaptation | ThemeManager for light and dark mode switching |
| License | The original article states CC 4.0 BY-SA |
| Star Count | Not provided in the original content |
| Core Dependencies | ArkUI, promptAction, application service layer |
The core value of this settings page lies in unifying interaction, state, and visual rules
A settings page is not a loose collection of controls. It is the control center of the application. A strong design should satisfy three goals at once: clear structure, immediate feedback, and persistent state. The original implementation is organized around five modules and covers the common settings needs of most mobile applications.
From an information architecture perspective, it uses a classic layout with top navigation and a scrollable content area. This approach keeps the cognitive load low, supports multiple grouped configuration sections, and makes it easy to expand with more card-based modules later.
The page skeleton should prioritize layout stability and a recognizable title
Row() {
Button('← Back')
.fontSize(18)
.backgroundColor(Color.Transparent) // Transparent back button reduces visual distraction
.onClick(() => this.goBack())
Blank() // Key point: use flexible empty space to naturally center the title
Text('Settings')
.fontSize(24)
.fontWeight(FontWeight.Bold)
}
.width('100%')
.padding(16)
This code builds the top navigation bar. The key detail is using Blank() to center the title naturally.
The volume settings module is best implemented with a Slider for continuous input
Volume is a typical numeric setting, so Slider is more natural than a button group. In this implementation, the value range is limited to 0 through 100, with a step size of 10. This reduces accidental input and lowers the burden of precise dragging.
Showing the current percentage in real time is critical for usability. When users drag the control and instantly see the value change, the feedback loop between action, understanding, and confirmation becomes much shorter.
Volume changes should stay synchronized with both UI state and persistent state
@State volume: number = 70
private onVolumeChange(value: number): void {
this.volume = value // Update UI state first to guarantee immediate feedback
this.appService.settingsManager.updateSettings({ volume: value }) // Then write to the persistence layer
}
This code connects UI updates with settings persistence and prevents mismatches between the displayed value and the stored value.
Voice toggle and difficulty selection represent two common models for discrete state
Voice prompts have only two states, on and off, so Toggle is the right fit. The design combines a primary label with supporting text to explain what happens when the option is enabled, which reduces guesswork around the meaning of the switch. The green selected state also matches common user expectations.
Difficulty selection is a finite enum-style value, so an equal-width button group works better. layoutWeight(1) lets all four buttons share the available space evenly, which improves alignment and keeps the layout stable across different screen widths.
Row({ space: 8 }) {
ForEach([5, 10, 15, 20], (count: number) => {
Button(`${count} times`)
.layoutWeight(1) // Distribute width evenly to keep the button group tidy
.backgroundColor(this.challengeCount === count ? '#FF9800' : '#F5F5F5')
.onClick(() => this.onChallengeCountChange(count))
})
}
This code generates discrete options in a loop and uses background color to indicate the current selection.
Side button configuration demonstrates the practical value of conditional rendering in complex settings pages
This module has two layers. The first controls whether the feature is enabled. The second reveals mode selection after the feature is turned on. This design avoids exposing irrelevant settings by default and significantly reduces visual noise.
Conditional rendering is more than a visual optimization. It also expresses business rules. The mode card appears only when sideButtonEnabled is true, so the UI behavior is effectively an external representation of a state machine.
Radio-style cards work well for communicating mode differences and explanatory copy
if (this.sideButtonEnabled) {
Button() {
Row({ space: 12 }) {
Text(this.sideButtonMode === SideButtonMode.BOTH ? '●' : '○')
Text('Show both left and right buttons')
}
}
.border({
width: this.sideButtonMode === SideButtonMode.BOTH ? 2 : 1, // Use border thickness to strengthen the selected state
color: this.sideButtonMode === SideButtonMode.BOTH ? '#4CAF50' : '#E0E0E0'
})
.onClick(() => this.onSideButtonModeChange(SideButtonMode.BOTH))
}
This code combines conditional rendering with card-style single selection, making the mode configuration both readable and clickable.
Data management operations must prevent accidental actions and make consequences explicit
Report and feedback is a low-risk action, so a standard card with Toast feedback is sufficient. Clearing all data is a high-risk action, so it must use warning colors, a confirmation step, and a clear explanation of consequences.
The most reusable principle here is to make dangerous actions explicit. The red button should only trigger confirmation, not delete data directly. The actual cleanup should happen only after the confirmation dialog.
private async showClearDataDialog(): Promise
<void> {
const result = await promptAction.showDialog({
title: 'Confirm Clear',
message: 'Are you sure you want to clear all data? This will delete all practice records and settings.', // Clearly explain the consequences
buttons: [{ text: 'Cancel' }, { text: 'Confirm', color: '#FF5722' }]
})
if (result.index === 1) {
await this.appService.storageService.clearAll() // Key step: clear storage first
this.appService.settingsManager.resetToDefaults() // Then restore the default settings
}
}
This code implements the full safety loop for a destructive action: confirm, execute, and reset.
The maintainability of a settings page ultimately depends on decoupled state management
The original implementation uses SettingsManager to manage the settings object centrally and broadcasts changes to the page through the observer pattern. This is more controllable than directly reading and writing local state across multiple components, and it works better for synchronization across pages.
Load the current settings when the page opens, and unsubscribe when the page is destroyed. These are key practices for preventing state drift and memory leaks. For ArkTS pages, this pattern is more suitable for long-term maintenance than scattered event callbacks.
The observer pattern enables globally consistent settings updates
export class SettingsManager {
private callbacks: Set<(settings: AppSettings) => void> = new Set()
updateSettings(updates: Partial
<AppSettings>): void {
// Core idea: handle settings updates and validation through a single entry point
this.notifyCallbacks()
}
onSettingsChange(callback: (settings: AppSettings) => void): () => void {
this.callbacks.add(callback)
return () => this.callbacks.delete(callback) // Return an unsubscribe function
}
}
This code centralizes settings updates into a single entry point, making validation, broadcasting, and future extension easier.
Dark mode adaptation should be centralized in the theme layer instead of scattered across components
Settings pages usually contain a high density of controls. If you hardcode colors inside components, adapting the interface for dark mode becomes painful later. A more robust approach is to abstract ThemeColors and let ThemeManager provide the current theme palette.
This brings two major benefits. First, it keeps the visual style consistent. Second, when the theme changes, you only update the color mapping instead of rewriting component logic one by one. This is especially important in large applications.
export class ThemeColors {
backgroundColor: string = '#FFFFFF'
cardBackground: string = '#FFFFFF'
textPrimary: string = '#333333'
textSecondary: string = '#666666'
}
if (this.isDarkMode) {
colors.backgroundColor = '#121212' // Dark background reduces glare
colors.cardBackground = '#1E1E1E'
colors.textPrimary = '#E0E0E0'
}
This code uses a theme color object to manage the visual mapping for both light and dark modes in one place.
This settings page approach works well as a reusable template for HarmonyOS applications
If you are building a practice app, utility app, or content app, you can reuse this structure directly: card-based grouping, declarative state binding, centralized settings management, and unified theme handling, combined with safeguards for destructive actions. Together, these pieces already form a solid engineering-ready foundation.
The real strength is not the individual controls. It is the way the implementation organizes UI presentation, business state, persistence, and the theme system into a structure that can evolve over time.
FAQ provides structured answers to common implementation questions
1. Why is it not recommended to keep all settings state directly in the page?
Because settings often need to be shared across pages and persisted. Centralizing them in SettingsManager makes validation, notifications, and default restoration consistent and reduces maintenance cost.
2. When should you use Toggle versus a button group in a HarmonyOS settings page?
Use Toggle for boolean states, such as voice prompts. Use a button group for finite discrete options, such as difficulty selection. For continuous numeric ranges, prefer Slider.
3. What is the most common pitfall in dark mode adaptation?
The most common issue is hardcoding colors inside components, which leads to missed cases and inconsistent updates. Abstract colors into ThemeManager so that the page consumes theme variables instead of concrete color values.
AI Readability Summary
This article reconstructs a HarmonyOS 6 settings page implementation with a focus on ArkTS declarative UI, state synchronization, dark mode adaptation, and safeguards for destructive actions. It covers core modules such as volume, voice prompts, difficulty, side buttons, and data management.