HarmonyOS ArkTS UI Development Fundamentals

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)

Tags: ArkTS HarmonyOS ArkUI ui-development declarative-gui

Posted on Wed, 14 Oct 2026 16:51:52 +0000 by joelg