# Custom Message List

This recipe shows how to replace only the message bubble layout while keeping all of `StreamMessageListView`'s built-in behavior (pagination, reactions, threads, typing indicators).

### Approach: messageBuilder with copyWith

Use the `messageBuilder` callback on `StreamMessageListView` to intercept each message and customize its props before rendering:

```dart
StreamMessageListView(
  messageBuilder: (context, message, defaultProps) {
    return StreamMessageItem.fromProps(
      props: defaultProps.copyWith(
        // Enable swipe-to-reply gesture
        swipeToReply: true,
        // Custom padding for the message bubble
        padding: const EdgeInsets.symmetric(
          horizontal: 16,
          vertical: 8,
        ),
      ),
    );
  },
)
```

### Approach: fully custom message widget

For a completely different layout, return your own widget from `messageBuilder`. Read the message from `defaultProps.message` and determine ownership by comparing the sender's ID against the current user:

```dart
StreamMessageListView(
  messageBuilder: (context, message, defaultProps) {
    final currentUserId = StreamChat.of(context).currentUser?.id;
    final isOwn = message.user?.id == currentUserId;
    return Align(
      alignment: isOwn ? Alignment.centerRight : Alignment.centerLeft,
      child: Container(
        margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
        padding: const EdgeInsets.all(12),
        decoration: BoxDecoration(
          color: isOwn ? Colors.blue : Colors.grey.shade200,
          borderRadius: BorderRadius.circular(16),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisSize: MainAxisSize.min,
          children: [
            if (!isOwn)
              Text(
                message.user?.name ?? '',
                style: const TextStyle(fontWeight: FontWeight.bold),
              ),
            Text(
              message.text ?? '',
              style: TextStyle(
                color: isOwn ? Colors.white : Colors.black87,
              ),
            ),
          ],
        ),
      ),
    );
  },
)
```

### Approach: app-wide via StreamComponentFactory

To apply a custom message layout throughout the entire app without modifying each `StreamMessageListView`, use `StreamComponentFactory`:

```dart
StreamComponentFactory(
  builders: StreamComponentBuilders(
    extensions: streamChatComponentBuilders(
      messageItem: (context, props) => MyCustomMessage(props: props),
    ),
  ),
  child: MyApp(),
)
```

### Tips

- Keep system and ephemeral messages (typing indicators, deleted messages) handled by `defaultProps` to avoid breaking built-in behavior.
- To check if a message belongs to the current user, compare `message.user?.id == StreamChat.of(context).currentUser?.id`.
- Reactions, threads, and editing are driven by `actionsBuilder` on `StreamMessageItemProps`. Copy the default actions and add or remove items rather than replacing the list entirely.


---

This page was last updated at 2026-06-09T15:44:08.064Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/flutter/cookbook/custom-message-list/](https://getstream.io/chat/docs/sdk/flutter/cookbook/custom-message-list/).