Custom Attributes¶
Beyond standard formatting, applications frequently require domain-specific styling or metadata (e.g. pastel highlighter pens, @mentions, #hashtags, editorial comment anchors, spoiler tags). Arranger enables developers to create custom AttributeKey<T> types and resolve them into Compose styles with complete compile-time type safety.

Four Implementation Steps¶
Introducing custom attributes involves four straightforward steps:
- Define Attribute Key: Implement
SpanAttributeKey<T>orParagraphAttributeKey<T>. - Add DSL Extensions: Define helper functions on
AttributeEditScope(optional). - Configure Style Resolver: Map the attribute key to Compose
SpanStyleorParagraphStyleinAttributeStyleResolver. - Render in Editor: Pass the custom resolver to
RichTextEditor(styleResolver = ...).
Implementation Example 1: Highlighter Pen (HighlightKey)¶
An example of a custom flag span attribute without a parameter value (Unit):
Step 1: Define Attribute Key¶
// Define as a singleton object or data object
object HighlightKey : SpanAttributeKey<Unit> {
override val name: String = "Highlight"
override val defaultValue: Unit = Unit
}
Step 2: Add DSL Extensions¶
Add extension functions so the attribute can be called intuitively inside edit { ... } blocks.
fun AttributeEditScope.highlight() {
setSpanAttribute(HighlightKey, Unit)
}
fun AttributeEditScope.clearHighlight() {
setSpanAttribute(HighlightKey, null)
}
Step 3: Configure Style Resolver¶
Using AttributeStyleResolver(base = DefaultAttributeStyleResolver) allows you to retain all default formatting rules (bold, underline, etc.) while layering in custom styling rules.
val CustomAttributeResolver = AttributeStyleResolver(base = DefaultAttributeStyleResolver) {
spanStyle(HighlightKey) {
SpanStyle(
background = Color(0xFFFFF59D), // Soft highlighter yellow
color = Color(0xFFE65100), // Deep orange text
fontWeight = FontWeight.Bold,
)
}
}
Step 4: Render in Editor¶
@Composable
fun CustomAttributeScreen() {
val text = "Arranger provides robust support for custom attributes."
val state = remember {
RichTextState(
initialText = RichString(text).edit {
editAttributes(range = text.rangeOf("custom attributes")) {
highlight()
}
}
)
}
RichTextEditor(
state = state,
styleResolver = CustomAttributeResolver,
modifier = Modifier.fillMaxWidth(),
)
}
Implementation Example 2: Metadata Attribute (Mentions)¶
You can also attach arbitrary structured objects (such as user IDs or metadata) as attribute values:
data class MentionData(val userId: String, val username: String)
object MentionKey : SpanAttributeKey<MentionData> {
override val name: String = "Mention"
override val defaultValue: MentionData = MentionData(userId = "", username = "")
}
// Define resolver mapping
val MentionStyleResolver = AttributeStyleResolver(base = DefaultAttributeStyleResolver) {
spanStyle(MentionKey) { data ->
SpanStyle(
color = Color(0xFF1976D2), // Blue link-style text
fontWeight = FontWeight.SemiBold,
)
}
}
When tapped, you can retrieve MentionData directly via event.span.attributes[MentionKey] to navigate to a user profile or display a hovercard.
Related Documentation¶
- Theming and Material 3: Style resolution architecture and Material 3 integration.
- Interactive Spans: Handling click events on custom attributes.
- Autocomplete: Inserting custom attributes via trigger suggestions.