DEV Community

HarmonyOS
HarmonyOS

Posted on • Edited on

ArkTS Best Practices: How to Build a Modular Theme Management System in 2025

Read the original article:ArkTS Best Practices: How to Build a Modular Theme Management System in 2025

Theme management on HarmonyOS Next mobile apps
​​

A complete guide on building modular, scalable, and reactive theme management in ArkTS for HarmonyOS Next applications.

Introduction

Have you ever tried switching between light and dark modes in your ArkTS app, only to find your components visually breaking or needing manual updates? In 2025, with HarmonyOS Next gaining traction, having a centralized and modular theme system isn’t just a nice-to-have — it’s a best practice.

This article walks you through building a scalable and reusable theme management system using ArkTS. We’ll show how it mirrors best practices from frameworks like Flutter, yet is tailored to HarmonyOS constraints.

In HarmonyOS Next development, especially for wearables or multi-device applications, a consistent theme is essential. Styling inconsistencies lead to poor user experience and maintenance overhead. Unlike Flutter’s Theme.of(context) or copyWith ArkTS lacks out-of-the-box theme support, which makes it important to implement a solid custom solution.

Description

We aim to:

  • Create centralized theme definitions

  • Allow runtime switching between Light and Dark modes

  • Provide reusable components that react to theme changes

  • Reduce code duplication

We’ll cover:

  • ThemeMode enum for mode management

  • ColorScheme and TextStyle for central styling

  • AppTheme as the core context holder

  • ThemeStore for reactivity

  • ThemedText, ThemedButton, and more

Solution / Approach

1. ThemeMode Enum

export enum ThemeMode {
  LIGHT,
  DARK,
}
Enter fullscreen mode Exit fullscreen mode

This helps us manage modes cleanly using strict types.

2. TextStyle & ComponentStyle Interfaces

export interface TextStyle {
  fontSize: number;
  fontWeight: number;
  color: string;
  fontFamily?: string;
}
Enter fullscreen mode Exit fullscreen mode

These define base styling contracts for text and UI elements.

3. ColorScheme & IColorSchemeOptions

export interface IColorSchemeOptions {
  primary: string;
  onPrimary: string;
  background: string;
  onBackground: string;
  surface: string;
  onSurface: string;
  error: string;
  onError: string;
}

export class ColorScheme {
  constructor(options: IColorSchemeOptions) {
    // Assign properties here
  }
}

Enter fullscreen mode Exit fullscreen mode

We use IColorSchemeOptions to clearly define required parameters.

4. Light & Dark Color Schemes

export const LightColorScheme = new ColorScheme({
  primary: '#1976D2',
  onPrimary: '#FFFFFF',
  background: '#FFFFFF',
  onBackground: '#000000',
  surface: '#F5F5F5',
  onSurface: '#000000',
  error: '#D32F2F',
  onError: '#FFFFFF'
});

export const DarkColorScheme = new ColorScheme({
  primary: '#90CAF9',
  onPrimary: '#000000',
  background: '#121212',
  onBackground: '#FFFFFF',
  surface: '#1E1E1E',
  onSurface: '#FFFFFF',
  error: '#CF6679',
  onError: '#000000'
});
Enter fullscreen mode Exit fullscreen mode

5. AppTheme Class

export class AppTheme {
  colors: ColorScheme;
  textTheme: TextTheme;
  componentTheme: ComponentTheme;

  constructor(mode: ThemeMode) {
    this.colors = mode === ThemeMode.DARK ? DarkColorScheme : LightColorScheme;
    this.textTheme = new TextTheme(this.colors);
    this.componentTheme = new ComponentTheme(this.colors);
  }
}

Enter fullscreen mode Exit fullscreen mode

AppTheme acts like the root context of all style-related definitions.

6. TextTheme and ComponentTheme

export class TextTheme {
  titleLarge: TextStyle;
  // ... other text styles
  constructor(colors: ColorScheme) {
    this.titleLarge = { fontSize: 24, fontWeight: 700, color: colors.primary };
  }
}

export class ComponentTheme {
  buttonStyle: ComponentStyle;
  constructor(colors: ColorScheme) {
    this.buttonStyle = {
      backgroundColor: colors.primary,
      fontColor: colors.onPrimary,
      borderRadius: 12,
      padding: 12
    };
  }
}

Enter fullscreen mode Exit fullscreen mode

7. ThemeStore (Reactive Store)

@Observed
export class ThemeStore {
  theme: AppTheme = new AppTheme(ThemeMode.LIGHT);

  setTheme(mode: ThemeMode) {
    this.theme = new AppTheme(mode);
  }
}
export const themeStore = new ThemeStore();

Enter fullscreen mode Exit fullscreen mode

Any UI using this store will automatically update on mode change.

8. Helper: applyTextStyle & copyWith

export function applyTextStyle(text: StylableText, style: TextStyle): StylableText {
  return text
    .fontSize(`${style.fontSize}fp`)
    .fontWeight(style.fontWeight)
    .fontColor(style.color)
    .fontFamily(style.fontFamily ?? 'HarmonyOS Sans');
}

export class TextStyleImpl implements TextStyle {
  // All props
  copyWith(overrides: Partial<TextStyle>): TextStyle {
    return new TextStyleImpl({ ...this, ...overrides });
  }
}

Enter fullscreen mode Exit fullscreen mode

9. Themed Components (Reusable)

@Component
export struct ThemedText {
  @Prop text: string;
  @Prop style: TextStyle;
  build() {
    Text(this.text)
      .fontSize(`${this.style.fontSize}fp`)
      .fontWeight(this.style.fontWeight)
      .fontColor(this.style.color)
  }
}

Enter fullscreen mode Exit fullscreen mode

These components consume styles from themeStore.theme.

Conclusion

Building a modular theme management system in ArkTS is not just about aesthetics — it’s about creating scalable, maintainable, and consistent experiences across your entire application.

By centralizing theme logic using AppTheme, ThemeStore, and reusable components like ThemedText developers can reduce duplication, simplify updates, and enable seamless light/dark mode switching.

If you’re building for HarmonyOS Next in 2025, this approach will save you time, prevent UI inconsistencies, and elevate your app’s user experience.

🧠 What’s next? Try extending this architecture with typography scaling, spacing tokens, or even user-selectable themes via settings.

💬 How are you handling themes in your ArkTS project? Share your tips or questions in the comments!

Key Takeaways

  • ArkTS lacks built-in theme systems, so we must build them.

  • Inspired by Flutter’s Theme.of() and copyWith.

  • Centralization improves consistency, scalability, and DX.

  • Reactive store enables real-time theme switching.

🧠 Pro Tip: You can also structure your themes as external resource files using and access them via , but modular runtime control is still easier using in-code object-based theme stores.resources/color.json$r()

References

(https://forums.developer.huawei.com/forumPortal/en/topic/0203189179269322002?fid=0102647487706140266)

✍️Written by Arif Emre Ankara

Top comments (1)

Collapse
 
taotaoling profile image
taotao

This article has been incredibly helpful to me, and I'm truly grateful for your valuable insights.