Attribute Modifier (AttributeModifier)
Overview
The introduction of the @Styles and @Extend decorators in declarative syntax helps with reuse of custom styles, but they encounter limitations in certain scenarios:
- Both @Styles and @Extend are processed at compile time and do not support cross-file exports for reuse.
- @Styles only supports universal attributes and events, not component-specific attributes.
- While @Styles allows for polymorphic styles, it does not support parameter passing, which means it cannot expose certain properties externally.
- @Extend supports private attributes and events of specific components, but it does not support cross-file exports for reuse either.
- Neither @Styles nor @Extend supports service logic for dynamically determining whether to set certain attributes. They only allow setting all possible attributes using ternary expressions, which is inefficient when dealing with a large number of attributes.
To address the above issues, ArkUI introduces the AttributeModifier mechanism, which allows for dynamic modification of attributes through Modifier objects. The table below is a comparison of the capabilities between the AttributeModifier mechanism and the @Styles and @Extend decorators.
| Capability | @Styles | @Extend | AttributeModifier |
|---|---|---|---|
| Cross-file export | Not supported | Not supported | Supported |
| Universal attribute setting | Supported | Supported | Supported |
| Universal event setting | Supported | Supported | Partially supported |
| Component-specific attribute setting | Not supported | Supported | Partially supported |
| Component-specific event setting | Not supported | Supported | Partially supported |
| Parameter passing | Not supported | Supported | Supported |
| Polymorphic styles | Supported | Not supported | Supported |
| Service logic | Not supported | Not supported | Supported |
Clearly, when compared to @Styles and @Extend, AttributeModifier provides superior capabilities and flexibility. As it continues to evolve to encompass a full spectrum of attribute and event settings, AttributeModifier is the preferred choice for implementation.
API Definition
declare interface AttributeModifier<T> {
applyNormalAttribute?(instance: T): void;
applyPressedAttribute?(instance: T): void;
applyFocusedAttribute?(instance: T): void;
applyDisabledAttribute?(instance: T): void;
applySelectedAttribute?(instance: T): void;
}
AttributeModifier is an API that requires you to implement methods in the form of applyXxxAttribute. Xxx signifies various states of polymorphism and can be in the following: Normal, Pressed, Focused, Disabled, and Selected. T represents the attribute type of the component. Within the callback, you can access the attribute object and use it to set the attributes.
declare class CommonMethod<T> {
attributeModifier(modifier: AttributeModifier<T>): T;
}
attributeModifier is a universal component method that allows you to pass in a custom modifier. Since the type T is explicitly defined when a component is instantiated, the type T passed to the method must be the corresponding attribute type for that component, or it must be CommonAttribute.
How to Use
- The attributeModifier method accepts an instance that implements the AttributeModifier<T> API. Here, T must be the specific attribute type corresponding to the component, or it must be CommonAttribute.
- When a component is initialized for the first time or when its associated state variable changes, if the passed instance implements the corresponding API, the applyNormalAttribute callback will be invoked.
- When the applyNormalAttribute callback is invoked, a component attribute object is passed in. Through this object, you can set the attributes and events of the current component.
- If an attempt is made to execute attributes or events that are not yet supported, an exception will be thrown during execution.
- When an attribute change triggers the applyXxxAttribute API, any attributes that were previously set on the component but not included in the current change will revert to their default values.
- The API can be used to leverage polymorphic styling capabilities. For example, if you need to set certain attributes when the component enters a pressed state, you can implement the applyPressedAttribute method to achieve this.
- If the same attribute is set on a component using both attribute methods and applyNormalAttribute, the principle of property override is followed, which means that the last set attributes take effect.
- A single Modifier instance object can be used across multiple components.
- If applyNormalAttribute is used multiple times on a single component with different Modifier instances, each time the state variables are updated, the attribute settings of these instances will be executed in the order they were applied, which also follows the principle of property override.
Setting and Modifying Component Attributes
AttributeModifier provides a powerful mechanism to separate the UI from styling. It enables the dynamic customization of component attributes with support for parameter passing and service logic writing, and triggers updates through state variables.
export class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
// A private member variable that can be dynamically modified externally
public isDark: boolean = false
// The constructor allows for parameter passing when creating an instance.
constructor(dark?: boolean) {
this.isDark = dark ?? false
}
applyNormalAttribute(instance: ButtonAttribute): void {
// instance is the attribute object for the Button, which can be modified here.
if (this.isDark) {// Service logic can be written here.
// After attribute changes trigger the apply function, attributes that were set before but not included in the change will revert to their default values.
instance.backgroundColor('#707070')
} else {
// Chaining of attribute methods is supported.
instance.backgroundColor('#17A98D')
.borderColor('#707070')
.borderWidth(2)
}
}
}
// pages/Button1.ets
import { MyButtonModifier } from '../Common/ButtonModifier01'
@Entry
@Component
struct Button1 {
// The modifier is decorated with @State, with behavior consistent with that of a regular object.
@State modifier: MyButtonModifier = new MyButtonModifier(true);
build() {
Row() {
Column() {
Button('Button')
.attributeModifier(this.modifier)
.onClick(() => {
// When the level-1 attribute of the modifier is changed, a UI update is triggered, causing applyNormalAttribute to be executed again.
this.modifier.isDark = !this.modifier.isDark
})
}
.width('100%')
}
.height('100%')
}
}

If the same attribute is set on a component using both attribute methods and applyNormalAttribute, the principle of property override is followed, which means that the last set attributes take effect.
export class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
// A private member variable that can be dynamically modified externally
public isDark: boolean = false
// The constructor allows for parameter passing when creating an instance.
constructor(dark?: boolean) {
this.isDark = dark ?? false
}
applyNormalAttribute(instance: ButtonAttribute): void {
// instance is the attribute object for the Button, which can be modified here.
if (this.isDark) {// Service logic can be written here.
// After attribute changes trigger the apply function, attributes that were set before but not included in the change will revert to their default values.
instance.backgroundColor('#707070')
} else {
// Chaining of attribute methods is supported.
instance.backgroundColor('#17A98D')
.borderColor('#707070')
.borderWidth(2)
}
}
}
// pages/Button2.ets
import { MyButtonModifier } from '../Common/ButtonModifier01'
@Entry
@Component
struct Button2 {
@State modifier: MyButtonModifier = new MyButtonModifier(true);
build() {
Row() {
Column() {
// As the attribute is set before the modifier, the button's color changes in accordance with the value of the modifier.
Button('Button')
.backgroundColor('#2787D9')
.attributeModifier(this.modifier)
.onClick(() => {
this.modifier.isDark = !this.modifier.isDark
})
}
.width('100%')
}
.height('100%')
}
}

If applyNormalAttribute is used multiple times on a single component with different Modifier instances, each time the state variables are updated, the attribute settings of these instances will be executed in the order they were applied, which also follows the principle of property override.
export class MyButtonModifier2 implements AttributeModifier<ButtonAttribute> {
public isDark: boolean = false
constructor(dark?: boolean) {
this.isDark = dark ?? false
}
applyNormalAttribute(instance: ButtonAttribute): void {
if (this.isDark) {
instance.backgroundColor(Color.Black)
.width(200)
} else {
instance.backgroundColor(Color.Red)
.width(100)
}
}
}
export class MyButtonModifier3 implements AttributeModifier<ButtonAttribute> {
public isDark2: boolean = false
constructor(dark?: boolean) {
this.isDark2 = dark ? dark : false
}
applyNormalAttribute(instance: ButtonAttribute): void {
if (this.isDark2) {
instance.backgroundColor('#2787D9')
} else {
instance.backgroundColor('#707070')
}
}
}
// pages/Button3.ets
import { MyButtonModifier2 } from '../Common/ButtonModifier02';
import { MyButtonModifier3 } from '../Common/ButtonModifier03';
@Entry
@Component
struct Button3 {
@State modifier: MyButtonModifier2 = new MyButtonModifier2(true);
@State modifier2: MyButtonModifier3 = new MyButtonModifier3(true);
build() {
Row() {
Column() {
Button('Button')
.attributeModifier(this.modifier)
.attributeModifier(this.modifier2)
.onClick(() => {
this.modifier.isDark = !this.modifier.isDark
this.modifier2.isDark2 = !this.modifier2.isDark2
})
}
.width('100%')
}
.height('100%')
}
}
Setting Polymorphic Styles and Events
You can use AttributeModifier to set polymorphic styles and events, which enables the reuse of event logic and supports various states such as Normal, Pressed, Focused, Disabled, and Selected. For example, if you need to set certain attributes when the component enters a pressed state, you can implement the applyPressedAttribute method to achieve this.
export class MyButtonModifier4 implements AttributeModifier<ButtonAttribute> {
applyNormalAttribute(instance: ButtonAttribute): void {
// instance is the attribute object for the Button, used to set attributes for the normal state.
instance.backgroundColor('#17A98D')
.borderColor('#707070')
.borderWidth(2)
}
applyPressedAttribute(instance: ButtonAttribute): void {
// instance is the attribute object for the Button, used to set attributes for the pressed state.
instance.backgroundColor('#2787D9')
.borderColor('#FFC000')
.borderWidth(5)
}
}
// pages/Button4.ets
import { MyButtonModifier4 } from '../Common/ButtonModifier04'
@Entry
@Component
struct Button4 {
@State modifier: MyButtonModifier4 = new MyButtonModifier4();
build() {
Row() {
Column() {
Button('Button')
.attributeModifier(this.modifier)
}
.width('100%')
}
.height('100%')
}
}
attributeModifier Support for Attributes and Events
Dynamic attribute and event configuration via attributeModifier is supported since API version 11.
Attributes and Events Not Supporting attributeModifier
The following table lists attributes and events that currently do not support attributeModifier. Unless otherwise specified, attributes and events support attributeModifier by default from their initial release.
| Component/Attribute Category | Attribute/Event Name | Error Message | Description |
|---|---|---|---|
| CommonAttribute | accessibilityText | - | - |
| CommonAttribute | accessibilityDescription | - | - |
| CommonAttribute | animation | Method not implemented. | Animation attributes are not supported. |
| CommonAttribute | attributeModifier | - | attributeModifier does not take effect when nested. |
| CommonAttribute | backgroundFilter | is not callable | - |
| CommonAttribute | chainWeight | is not callable | - |
| CommonAttribute | compositingFilter | is not callable | - |
| CommonAttribute | drawModifier | is not callable | Modifier attributes are not supported. |
| CommonAttribute | foregroundFilter | is not callable | - |
| CommonAttribute | freeze | is not callable | - |
| CommonAttribute | gesture | Method not implemented. | Gesture attributes are not supported. |
| CommonAttribute | gestureModifier | is not callable | Modifier attributes are not supported. |
| CommonAttribute | onAccessibilityHover | is not callable | - |
| CommonAttribute | onDigitalCrown | is not callable. | - |
| CommonAttribute | parallelGesture | Method not implemented. | Gesture attributes are not supported. |
| CommonAttribute | priorityGesture | Method not implemented. | Gesture attributes are not supported. |
| CommonAttribute | reuseId | Method not implemented. | - |
| CommonAttribute | stateStyles | Method not implemented. | stateStyles attributes are not supported. |
| CommonAttribute | useSizeType | Method not implemented. | Deprecated attributes are not supported. |
| CommonAttribute | visualEffect | is not callable | - |
| CommonAttribute | bindContextMenu | Method not implemented. | Attributes that accept a CustomBuilder are not supported. |
| CommonAttribute | bindContentCover | Method not implemented. | Attributes that accept a CustomBuilder are not supported. |
| CommonAttribute | bindSheet | Method not implemented. | Attributes that accept a CustomBuilder are not supported. |
| CommonAttribute | dragPreview | Builder is not supported. | Attributes that accept a CustomBuilder are not supported. |
| CommonAttribute | bindPopup | Method not implemented. | Attributes that accept a CustomBuilder are not supported. |
| CommonAttribute | accessibilityVirtualNode | is not callable | Attributes that accept a CustomBuilder are not supported. |
| CommonAttribute | chainWeight | - | - |
| CheckboxGroup | contentModifier | - | - |
| CommonAttribute | backgroundImage | - | - |
| CommonAttribute | onClick | - | - |
| CommonAttribute | toolbar | - | - |
| CommonAttribute | accessibilityGroup | - | - |
| CommonAttribute | reuse | - | - |
| CommonAttribute | onGestureRecognizerJudgeBegin | - | - |
| EmbeddedComponent | onError | - | - |
| EmbeddedComponent | onTerminated | - | - |
| NavDestination | backButtonIcon19+ | - | - |
| NavDestination | menus19+ | - | - |
| NavDestination | customTransition | - | - |
| Navigation | backButtonIcon | - | - |
| Navigation | menus | - | - |
| Repeat | each | - | - |
| Repeat | key | - | - |
| Repeat | virtualScroll | - | - |
| Repeat | template | - | - |
| Repeat | templateId | - | - |
| Search | customKeyboard | - | - |
| Search | onWillAttachIME | - | - |
| Select | menuItemContentModifier12+ | - | - |
| Select | menuItemContentModifier18+ | - | - |
| Select | textModifier | - | - |
| Select | arrowModifier | - | - |
| Select | optionTextModifier | - | - |
| Select | selectedOptionTextModifier | - | - |
| Slider | digitalCrownSensitivity | - | - |
| Swiper | prevMargin | - | - |
| Swiper | nextMargin | - | - |
| TextArea | customKeyboard | - | - |
| Text | bindSelectionMenu | - | - |
| TextInput | customKeyboard | - | - |
| TextInput | onWillAttachIME | - | - |
| TextPicker | onEnterSelectedArea | - | - |
| TimePicker | onEnterSelectedArea | - | - |
Version Differences Between Attribute/Event Initial Release and attributeModifier Support
The following table shows cases where the initial release version of an attribute or event differs from its attributeModifier support version. Unless otherwise specified, attributes and events support attributeModifier by default from their initial release.
| Component/Attribute Category | Attribute/Event Name | Initial Release Version | attributeModifier Support Version |
|---|---|---|---|
| AlphabetIndexer | autoCollapse | 11 | 12 |
| Button | buttonStyle | 11 | 12 |
| Button | controlSize | 11 | 12 |
| CalendarPicker | onChange | 18 | 20 |
| Canvas | enableAnalyzer | 12 | 20 |
| CommonAttribute | accessibilityTextHint | 12 | 20 |
| CommonAttribute | accessibilityChecked | 13 | 20 |
| CommonAttribute | accessibilitySelected | 13 | 20 |
| CommonAttribute | background | 10 | 20 |
| CommonAttribute | visualEffect | 12 | 20 |
| CommonAttribute | onDragStart | 8 | 13 |
| CommonAttribute | onVisibleAreaApproximateChange | 17 | 23 |
| CommonAttribute | onVisibleAreaChange | 9 | 20 |
| CommonAttribute | onTouchIntercept | 12 | 20 |
| CommonAttribute | onPreDrag | 12 | 20 |
| CommonAttribute | onChildTouchTest | 11 | 20 |
| CommonAttribute | backgroundFilter | 12 | 20 |
| CommonAttribute | foregroundFilter | 12 | 20 |
| CommonAttribute | compositingFilter | 12 | 20 |
| CommonAttribute | foregroundBlurStyle | 10 | 18 |
| CommonAttribute | freeze12+ | 12 | 20 |
| CommonAttribute | freeze18+ | 18 | 20 |
| CommonAttribute | dragPreviewOptions | 11 | 12 |
| CommonAttribute | bindMenu | 11 | 20 |
| CommonAttribute | transition | 12 | 20 |
| CommonAttribute | safeAreaPadding | 14 | 18 |
| CommonAttribute | pixelRound | 11 | 12 |
| ContainerSpan | textBackgroundStyle | 11 | 12 |
| DatePicker | onDateChange | 18 | 20 |
| FolderStack | alignContent | 11 | 12 |
| FolderStack | onFolderStateChange | 11 | 20 |
| FolderStack | onHoverStatusChange | 11 | 20 |
| FolderStack | enableAnimation | 11 | 12 |
| FolderStack | autoHalfFold | 11 | 12 |
| Gauge | privacySensitive | 12 | 20 |
| Image | enableAnalyzer | 11 | 12 |
| Image | resizable | 11 | 20 |
| List | OnScrollVisibleContentChangeCallback | 12 | 14 |
| List | onItemDragStart | 8 | 14 |
| NavDestination | title | 9 | 12 |
| NavDestination | mode | 11 | 12 |
| NavDestination | backButtonIcon11+ | 11 | 12 |
| NavDestination | menus12+ | 12 | 14 |
| NavDestination | toolbarConfiguration | 13 | 20 |
| NavDestination | onReady | 11 | 20 |
| NavDestination | onWillAppear | 12 | 20 |
| NavDestination | onWillDisappear | 12 | 20 |
| NavDestination | onWillShow | 12 | 20 |
| NavDestination | onWillHide | 12 | 20 |
| NavDestination | systemBarStyle | 12 | 20 |
| NavDestination | onResult | 15 | 22 |
| NavDestination | bindToScrollable | 14 | 22 |
| NavDestination | bindToNestedScrollable | 14 | 22 |
| NavDestination | onActive | 17 | 22 |
| NavDestination | onInactive | 17 | 22 |
| NavDestination | onNewParam | 19 | 22 |
| Navigation | title | 8 | 12 |
| Navigation | toolbarConfiguration | 10 | 20 |
| Navigation | customNavContentTransition | 11 | 20 |
| Navigation | systemBarStyle | 12 | 20 |
| PatternLock | backgroundColor | 9 | 20 |
| PatternLock | onDotConnect | 11 | 20 |
| Progress | privacySensitive | 12 | 20 |
| Refresh | onOffsetChange | 12 | 20 |
| RichEditor | customKeyboard | 10 | 23 |
| RichEditor | onDidIMEInput | 12 | 20 |
| RichEditor | enablePreviewText | 12 | 18 |
| RichEditor | placeholder | 12 | 18 |
| RichEditor | onWillChange | 12 | 18 |
| RichEditor | onDidChange | 12 | 18 |
| RichEditor | editMenuOptions | 12 | 18 |
| RichEditor | enableKeyboardOnFocus | 12 | 18 |
| RichEditor | enableHapticFeedback | 13 | 20 |
| RichEditor | barState | 13 | 18 |
| Select | menuBackgroundColor | 11 | 12 |
| Select | menuBackgroundBlurStyle | 11 | 12 |
| Swiper | displayCount | 8 | 12 |
| SymbolGlyph | fontSize | 11 | 12 |
| SymbolGlyph | fontColor | 11 | 12 |
| SymbolGlyph | fontWeight | 11 | 12 |
| SymbolGlyph | effectStrategy | 11 | 12 |
| SymbolGlyph | renderingStrategy | 11 | 12 |
| SymbolSpan | fontSize | 11 | 12 |
| SymbolSpan | fontColor | 11 | 12 |
| SymbolSpan | fontWeight | 11 | 12 |
| SymbolSpan | effectStrategy | 11 | 12 |
| SymbolSpan | renderingStrategy | 11 | 12 |
| ScrollableCommonAttribute | onWillScroll | 12 | 14 |
| ScrollableCommonAttribute | onDidScroll | 12 | 14 |
| TabContent | onWillShow | 12 | 20 |
| TabContent | onWillHide | 12 | 20 |
| Tabs | edgeEffect | 12 | 17 |
| Tabs | customContentTransition | 11 | 20 |
| Tabs | onContentWillChange | 12 | 20 |
| Tabs | barBackgroundBlurStyle | 11 | 12 |
| TextArea | enterKeyType | 11 | 12 |
| Text | enableHapticFeedback | 13 | 18 |
| TextInput | showCounter | 11 | 12 |
| TextInput | onSecurityStateChange | 12 | 20 |
| TextPicker | onScrollStop14+ | 14 | 20 |
| TextPicker | onScrollStop18+ | 18 | 20 |
| TextTimer | textShadow | 11 | 12 |
| TimePicker | enableHapticFeedback | 12 | 18 |
| TimePicker | onChange | 18 | 20 |
| Video | enableAnalyzer | 12 | 20 |
| Video | analyzerConfig | 12 | 20 |
| Video | onError | 7 | 20 |
| WaterFlow | onScrollIndex | 11 | 20 |