ArkTS is the primary language for building user interfaces on HarmonyOS, powered by the ArkUI declarative framework. This guide covers essential UI construction patterns, component composition, state management, layout systems, and advanced interaction techniques.
Custom Component Architecture
Components in ArkTS are defined using structural decorators. Every UI page begins with an @Entry decorator—only one per file—and uses @Component to declare reusable units.
@Entry
@Component
struct HomePage {
build() {
Column() {
Text("Welcome to HarmonyOS")
.fontSize(24)
.fontColor(Color.Blue)
Button("Get Started")
.onClick(() => {
router.pushUrl({ url: "pages/Detail" })
})
}
}
}
Reusable subcomponents follow the same pattern but omit @Entry:
@Component
struct HeaderBar {
private title: string = "Default Title"
build() {
Row() {
Text(this.title)
.fontWeight(FontWeight.Bold)
.fontSize(20)
}
.height(60)
.backgroundColor(Color.Gray)
}
}
Core UI Components
Text & Input Elements
The Text component supports internationalization via resource references and rich styling:
Text($r("app.string.welcome_message"))
.fontSize(18)
.fontColor(Color.Black)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.textAlign(TextAlign.Center)
For editable content, TextInput enables two-way binding and event handling:
struct InputForm {
@State userInput: string = ""
build() {
TextInput({
placeholder: $r("app.string.enter_name"),
text: $$.userInput
})
.onChange((value) => {
this.userInput = value
})
.onSubmit(() => {
console.info(`Submitted: ${this.userInput}`)
})
}
}
Image Handling
Images support four loading strategies:
- Rseource bundle:
Image($r("app.media.logo")) - Rawfile directory:
Image($rawfile("icon.png")) - Relative path:
Image("./assets/icon.png") - Remote URL (requires INTERNET permission):
Image("https://example.com/image.jpg")
Display behavior is controlled via objectFit:
Image($r("app.media.banner"))
.objectFit(ImageFit.Cover)
.aspectRatio(16 / 9)
.borderRadius(8)
Layout Systems
Flex-Based Arrangement
Column and Row serve as foundational layout containers with alignment controls:
Column({ space: 16 }) {
Text("Header").fontSize(20)
Row({ space: 8 }) {
Button("Action 1")
Button("Action 2")
}
}
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
Advanced Layouts
Stack enables layered composition with z-index control:
Stack({ alignContent: Alignment.TopStart }) {
Image($r("app.media.background"))
.width("100%")
.height("100%")
Text("Overlay Text")
.zIndex(1)
.fontColor(Color.White)
}
Grid provides responsive column-row arrangements:
Grid() {
ForEach([0, 1, 2, 3], (index) => {
GridItem() {
Card() {
Text(`Item ${index}`)
}
}
.rowStart(index + 1)
})
}
.rowsTemplate('1fr 1fr')
.columnsTemplate('1fr 1fr')
State Management & Data Binding
Reactive Properties
Different decorators manage data flow across component hierarchies:
@State: Local reactive state@Prop: Read-only parent-to-child binding@Link: Two-way binding between parent and child@StorageLink: Application-scoped persistent state
@Component
struct CounterDisplay {
@State count: number = 0
build() {
Column() {
Text(`Count: ${this.count}`)
Button("Increment")
.onClick(() => { this.count++ })
}
}
}
Persistent Storage
For cross-session persistence, use PersistentStorage:
// Initialize in EntryAbility
PersistentStorage.PersistProp("user_preference", "dark")
// Access in component
@StorageLink("user_preference")
themeMode: string = "light"
Interaction & Animation
Gestures
Gesture composition enables complex interactions:
struct InteractiveCard {
@State scale: number = 1
@State rotation: number = 0
build() {
Image($r("app.media.icon"))
.scale({ x: this.scale, y: this.scale })
.rotate({ angle: this.rotation })
.gesture(
GestureGroup(GestureMode.Parallel,
PinchGesture()
.onActionUpdate((e) => { this.scale = e.scale })
,
RotationGesture()
.onActionUpdate((e) => { this.rotation = e.angle })
)
)
}
}
Animation System
Declarative animations respond to state changes:
Button("Animate")
.onClick(() => {
animateTo({
duration: 800,
curve: Curve.EaseInOut
}, () => {
this.isActive = !this.isActive
})
})
if (this.isActive) {
Text("Active State")
.transition({ type: TransitionType.All, opacity: 1 })
} else {
Text("Inactive State")
.transition({ type: TransitionType.All, opacity: 0.3 })
}
Navigation & Routing
The router module handles page transitions:
import router from '@ohos.router'
// Navigate with parameters
router.pushUrl({
url: 'pages/Profile',
params: { userId: 123 }
})
// Handle incoming parameters
const params = router.getParams()
console.info(`User ID: ${params.userId}`)
Dialogs & Popups
Modal interactions use specialized components:
// Custom dialog
AlertDialog.show({
title: $r("app.string.confirm_action"),
message: $r("app.string.are_you_sure"),
primaryButton: {
value: $r("app.string.confirm"),
action: () => { this.performAction() }
},
secondaryButton: {
value: $r("app.string.cancel")
}
})
Threading & Performance
Off-main-thread operations prevent UI jank:
// Concurrent function (defined outside component)
@Concurrent
function processImageData(files: string[], outputPath: string): Promise<string[]> {
// Heavy computation here
return new Promise(resolve => {
setTimeout(() => resolve(files.map(f => f + "_processed")), 2000)
})
}
// In component method
async handleUpload() {
const task = new taskpool.Task(processImageData, this.imageFiles, this.tempPath)
const result = await taskpool.execute(task)
this.processedImages = result
}
Web Integration
WebView bridges native and web content:
Web({
src: $rawfile("dashboard.html"),
controller: this.webController
})
.javaScriptProxy({
name: "nativeBridge",
object: this.bridgeObject,
controller: this.webController,
methodList: ["fetchUserData", "logEvent"]
})
Environment Awareness
Adapt UI based on device characteristics:
// Detect dark mode
@Environment.EnvProp('colorMode', 'light')
colorMode: string = 'light'
// Apply theme
.backgroundColor(this.colorMode === 'dark' ? Color.Black : Color.White)