# Message

Customizing Messages with the Component Factory

### Introduction

Every application provides a unique look and feel to their own messaging interface. In v10 (design-refresh), message customization uses a centralized **component factory** pattern instead of per-widget builder callbacks.

The component factory provides:

- App-wide message customization without repeating configuration on every widget
- A consistent, composable sub-component architecture
- The ability to replace individual sub-components (avatar, footer, reactions, text)

### Using Component Builders

The simplest way to register app-wide component builders is via the `componentBuilders` parameter on `StreamChat`:

```dart
StreamChat(
  client: client,
  componentBuilders: StreamComponentBuilders(
    extensions: streamChatComponentBuilders(
      messageWidget: (context, props) {
        return DefaultStreamMessage(
          props: props.copyWith(
            actionsBuilder: (context, defaultActions) {
              return [
                ...defaultActions,
                StreamContextMenuAction(
                  leading: const Icon(Icons.star),
                  label: const Text('Favourite'),
                  onTap: () => _favourite(props.message),
                ),
              ];
            },
          ),
        );
      },
    ),
  ),
  child: const MyHomePage(),
)
```

For subtree-scoped overrides (e.g., customizing builders only in one part of the app), wrap that subtree with `StreamComponentFactory` directly:

```dart
StreamComponentFactory(
  builders: StreamComponentBuilders(
    extensions: streamChatComponentBuilders(
      messageWidget: (context, props) => MySpecialMessage(props: props),
    ),
  ),
  child: const ChatDetailScreen(),
)
```

### Sub-Components

`DefaultStreamMessage` is composed of named sub-components you can replace individually:

| Sub-component                | Description                                             |
| ---------------------------- | ------------------------------------------------------- |
| `DefaultStreamMessage`       | Top-level default renderer; composes all sub-components |
| `StreamMessageContent`       | Bubble, attachments, text, reactions, thread replies    |
| `StreamMessageFooter`        | Username, timestamp, sending status, edited indicator   |
| `StreamMessageLeading`       | Author avatar                                           |
| `StreamMessageReactions`     | Clustered reaction chips around the bubble              |
| `StreamMessageText`          | Markdown-rendered message text                          |
| `StreamMessageDeleted`       | Deleted message placeholder                             |
| `StreamMessageSendingStatus` | Delivery status icon                                    |

### Per-List Customization

For per-list customization, use `messageBuilder` on `StreamMessageListView`. The callback now receives `StreamMessageWidgetProps` instead of a pre-built widget:

```dart
StreamMessageListView(
  messageBuilder: (context, message, defaultProps) {
    // Build default widget (goes through component factory)
    return StreamMessageWidget.fromProps(props: defaultProps);
  },
),
```

Customize props before building:

```dart
StreamMessageListView(
  messageBuilder: (context, message, defaultProps) {
    return StreamMessageWidget.fromProps(
      props: defaultProps.copyWith(
        actionsBuilder: (context, actions) => [...actions, myAction],
      ),
    );
  },
),
```

Or replace entirely with your own widget:

```dart
StreamMessageListView(
  messageBuilder: (context, message, defaultProps) {
    return MyCustomMessageWidget(message: message);
  },
),
```

### Custom Reaction Icons

Configure custom reactions globally via `reactionIconResolver`. Extend `DefaultReactionIconResolver` and override only what you need:

```dart
class MyReactionIconResolver extends DefaultReactionIconResolver {
  const MyReactionIconResolver();

  @override
  Set<String> get defaultReactions => const {'like', 'love', 'celebrate'};

  @override
  String? emojiCode(String type) {
    if (type == 'celebrate') return '🎉';
    return super.emojiCode(type);
  }
}

StreamChat(
  client: client,
  streamChatConfigData: StreamChatConfigurationData(
    reactionIconResolver: const MyReactionIconResolver(),
  ),
  child: ...,
)
```

### Custom Attachment Builders

Register custom attachment builders globally:

```dart
class LocationAttachmentBuilder extends StreamAttachmentWidgetBuilder {
  @override
  bool canHandle(
    Message message,
    Map<String, List<Attachment>> attachments,
  ) {
    final locationAttachments = attachments['location'];
    return locationAttachments != null && locationAttachments.isNotEmpty;
  }

  @override
  Widget build(
    BuildContext context,
    Message message,
    Map<String, List<Attachment>> attachments,
  ) {
    final attachment = attachments['location']!.first;
    return LocationMapWidget(
      latitude: attachment.extraData['latitude'] as double,
      longitude: attachment.extraData['longitude'] as double,
    );
  }
}

StreamChat(
  client: client,
  streamChatConfigData: StreamChatConfigurationData(
    attachmentBuilders: [
      LocationAttachmentBuilder(),
      ...defaultAttachmentBuilders,
    ],
  ),
  child: ...,
)
```

### Theming

Message styling is split across two theme layers:

**Design-system level** — `StreamMessageItemThemeData` controls structural visibility and layout. It is part of `StreamTheme` (from `stream_core_flutter`) and can be overridden for a subtree using `StreamMessageItemTheme`:

```dart
StreamMessageItemTheme(
  data: StreamMessageItemThemeData(
    leadingVisibility: StreamMessageStyleVisibility(
      incoming: StreamVisibility.visible,
      outgoing: StreamVisibility.gone,
    ),
    footerVisibility: StreamMessageStyleVisibility(
      incoming: StreamVisibility.visible,
      outgoing: StreamVisibility.visible,
    ),
    incoming: StreamMessageItemStyle(
      padding: const EdgeInsets.all(4),
      backgroundColor: Colors.white,
    ),
    outgoing: StreamMessageItemStyle(
      padding: const EdgeInsets.all(4),
      backgroundColor: Colors.blue.shade50,
    ),
  ),
  child: StreamMessageListView(controller: controller),
)
```

**Chat-specific level** — `StreamMessageThemeData` controls text styles, colors, and link appearance for own and other messages. It is configured via `StreamChatThemeData`:

```dart
StreamChatThemeData(
  ownMessageTheme: StreamMessageThemeData(
    messageTextStyle: const TextStyle(color: Colors.white),
    messageBackgroundColor: Colors.blue,
  ),
  otherMessageTheme: StreamMessageThemeData(
    messageTextStyle: const TextStyle(color: Colors.black87),
    messageBackgroundColor: Colors.grey.shade200,
  ),
)
```

### Customizing the Channel List Item via Component Factory

The component factory also handles channel list items:

```dart
StreamComponentFactory(
  builders: StreamComponentBuilders(
    extensions: streamChatComponentBuilders(
      channelListItem: (context, props) => StreamChannelListTile(
        avatar: StreamChannelAvatar(channel: props.channel),
        title: Text(props.channel.name ?? ''),
        onTap: props.onTap,
        onLongPress: props.onLongPress,
        selected: props.selected,
      ),
    ),
  ),
  child: ...,
)
```


---

This page was last updated at 2026-04-23T18:43:03.994Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/flutter/v10/stream_chat_flutter/custom_widgets/customize_message_widget/](https://getstream.io/chat/docs/sdk/flutter/v10/stream_chat_flutter/custom_widgets/customize_message_widget/).