Fliin
On this page

Blocks / messaging

Comment row

Shows one comment under a post: avatar, name, age and text, replies indented on a single level, posting or failed with retry, and its actions as links.

16 of 16 variants pass
Comment row, comment state
Platform
Brightness
Text scale
Direction

Import

Comment row is part of Fliin Pro. With a license key, sign in once with fliin login, then add it in a Flutter app where fliin init has run:

fliin login
fliin add comment-row

Also installs: core, avatar, button, icons, link-button, chip

Examples

Comment

A comment under a post, with its author, time and a Reply action.

Comment row, comment state
import 'package:your_app/ui/fliin/components/comment_row/fl_comment_row.dart';
import 'package:flutter/widgets.dart';

class CommentRowCommentExample extends StatelessWidget {
  const CommentRowCommentExample({super.key});

  @override
  Widget build(BuildContext context) {
    return FlCommentRow(
      author: 'Hugo Martin',
      initials: 'HM',
      time: '12 min',
      text: 'Lovely light, was it taken from the balcony?',
      actions: [FlCommentAction(label: 'Reply', onPressed: () {})],
    );
  }
}

Reply

reply: true indents an answer under the comment it replies to.

Comment row, reply state
import 'package:your_app/ui/fliin/components/comment_row/fl_comment_row.dart';
import 'package:flutter/widgets.dart';

class CommentRowReplyExample extends StatelessWidget {
  const CommentRowReplyExample({super.key});

  @override
  Widget build(BuildContext context) {
    return FlCommentRow(
      author: 'Leah Bernard',
      initials: 'LB',
      time: '8 min',
      text: 'Yes, around seven.',
      reply: true,
      actions: [FlCommentAction(label: 'Reply', onPressed: () {})],
    );
  }
}

Sending

The user's own comment while it posts: FlCommentStatus.sending dims it and statusLabel says what is happening.

Comment row, sending state
import 'package:your_app/ui/fliin/components/comment_row/fl_comment_row.dart';
import 'package:flutter/widgets.dart';

class CommentRowSendingExample extends StatelessWidget {
  const CommentRowSendingExample({super.key});

  @override
  Widget build(BuildContext context) {
    return const FlCommentRow(
      author: 'You',
      initials: 'CD',
      time: 'Now',
      text: 'Love the framing.',
      status: FlCommentStatus.sending,
      statusLabel: 'Posting…',
    );
  }
}

Failed

A comment that could not be posted: FlCommentStatus.failed marks it, and its actions offer to try again or delete it.

Comment row, failed state
import 'package:your_app/ui/fliin/components/comment_row/fl_comment_row.dart';
import 'package:flutter/widgets.dart';

class CommentRowFailedExample extends StatelessWidget {
  const CommentRowFailedExample({super.key});

  @override
  Widget build(BuildContext context) {
    return FlCommentRow(
      author: 'You',
      initials: 'CD',
      time: 'Now',
      text: 'Shall we go back on Saturday?',
      status: FlCommentStatus.failed,
      statusLabel: 'Not posted',
      actions: [
        FlCommentAction(label: 'Try again', onPressed: () {}),
        FlCommentAction(label: 'Delete', onPressed: () {}),
      ],
    );
  }
}

Liked

Comments as social apps show them: the author's photo, authorBadge after the name of the post's author, and a heart with its count at the end of the row (likeLabel, likes, liked, onLike).

Comment row, liked state
import 'package:your_app/ui/fliin/components/comment_row/fl_comment_row.dart';
import 'package:your_app/ui/fliin/components/link_button/fl_link_button.dart';
import 'package:your_app/ui/fliin/core/fliin.dart';
import 'package:flutter/widgets.dart';

class CommentRowLikedExample extends StatefulWidget {
  const CommentRowLikedExample({super.key});

  @override
  State<CommentRowLikedExample> createState() => _CommentRowLikedExampleState();
}

class _CommentRowLikedExampleState extends State<CommentRowLikedExample> {
  var _liked = true;

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      spacing: FlScope.of(context).space.lg,
      children: [
        FlCommentRow(
          author: 'John Doe',
          initials: 'JD',
          avatar: const AssetImage('images/people/2.jpg'),
          time: '12 min',
          text: 'Was this taken from the balcony?',
          likeLabel: 'Like',
          likes: _liked ? '24' : '23',
          liked: _liked,
          onLike: () => setState(() => _liked = !_liked),
          actions: [FlCommentAction(label: 'Reply', tone: FlLinkButtonTone.muted, onPressed: () {})],
        ),
        FlCommentRow(
          author: 'Kate Wilson',
          initials: 'KW',
          avatar: const AssetImage('images/people/1.jpg'),
          authorBadge: 'Author',
          time: '8 min',
          text: 'Yes, around seven in the morning.',
          reply: true,
          replyLabel: 'Reply to John Doe',
          likeLabel: 'Like',
          likes: '3',
          onLike: () {},
        ),
      ],
    );
  }
}

Usage

The code the reference images render:

final example = ColoredBox(
  color: FlScope.of(context).colors.surface,
  child: SingleChildScrollView(
    padding: EdgeInsetsDirectional.all(FlScope.of(context).space.lg),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      spacing: FlScope.of(context).space.lg,
      children: [
        FlCommentRow(
          author: 'Leah Bernard',
          initials: 'LB',
          time: (variant.dir == TextDirection.rtl ? "12 د" : "12 min"),
          text: (variant.dir == TextDirection.rtl
              ? "ضوء رائع، هل التُقطت من الشرفة؟"
              : "Beautiful light, was it taken from the balcony?"),
          actions: [
            FlCommentAction(
              label: (variant.dir == TextDirection.rtl ? "رد" : "Reply"),
              tone: FlLinkButtonTone.muted,
              onPressed: () {},
            ),
          ],
        ),
        FlCommentRow(
          author: 'Hugo Martin',
          initials: 'HM',
          avatarColor: FlAvatarColor.success,
          time: (variant.dir == TextDirection.rtl ? "8 د" : "8 min"),
          text: (variant.dir == TextDirection.rtl
              ? "نعم، نحو السابعة."
              : "Yes, around seven."),
          reply: true,
          actions: [
            FlCommentAction(
              label: (variant.dir == TextDirection.rtl ? "رد" : "Reply"),
              tone: FlLinkButtonTone.muted,
              onPressed: () {},
            ),
          ],
        ),
        FlCommentRow(
          author: (variant.dir == TextDirection.rtl ? "أنت" : "You"),
          initials: 'CD',
          avatarColor: FlAvatarColor.accent,
          avatarVariant: FlAvatarVariant.soft,
          time: (variant.dir == TextDirection.rtl ? "الآن" : "Now"),
          text: (variant.dir == TextDirection.rtl
              ? "أحب هذا الإطار."
              : "Love the framing."),
          status: FlCommentStatus.sending,
          statusLabel: (variant.dir == TextDirection.rtl
              ? "جارٍ النشر…"
              : "Posting…"),
        ),
        FlCommentRow(
          author: (variant.dir == TextDirection.rtl ? "أنت" : "You"),
          initials: 'CD',
          avatarColor: FlAvatarColor.accent,
          avatarVariant: FlAvatarVariant.soft,
          time: (variant.dir == TextDirection.rtl ? "الآن" : "Now"),
          text: (variant.dir == TextDirection.rtl
              ? "هل نعود يوم السبت؟"
              : "Shall we go back on Saturday?"),
          reply: true,
          status: FlCommentStatus.failed,
          statusLabel: (variant.dir == TextDirection.rtl
              ? "لم يُنشر"
              : "Not posted"),
          actions: [
            FlCommentAction(
              label: (variant.dir == TextDirection.rtl
                  ? "أعد المحاولة"
                  : "Try again"),
              onPressed: () {},
            ),
            FlCommentAction(
              label: (variant.dir == TextDirection.rtl ? "حذف" : "Delete"),
              tone: FlLinkButtonTone.danger,
              onPressed: () {},
            ),
          ],
        ),
      ],
    ),
  ),
);

API

FlCommentStatus: enum {sending, failed}
FlCommentStatus.sending: static final FlCommentStatus
FlCommentStatus.failed: static final FlCommentStatus
FlCommentAction: final class
FlCommentAction.new: const new({required String label, required VoidCallback onPressed, String? semanticLabel, FlLinkButtonTone tone})
FlCommentAction.label: get String
FlCommentAction.semanticLabel: get String?
FlCommentAction.onPressed: get VoidCallback
FlCommentAction.tone: get FlLinkButtonTone
FlCommentRow: class extends StatelessWidget
FlCommentRow.new: const new({super.key, required String author, required String initials, ImageProvider? avatar, FlAvatarColor avatarColor, FlAvatarVariant avatarVariant, required String time, required String text, bool reply, String? replyLabel, FlCommentStatus? status, String? statusLabel, List<FlCommentAction> actions, String? authorBadge, String? likeLabel, String likes, bool liked, VoidCallback? onLike})
FlCommentRow.author: get String
FlCommentRow.initials: get String
FlCommentRow.avatar: get ImageProvider?
FlCommentRow.avatarColor: get FlAvatarColor
FlCommentRow.avatarVariant: get FlAvatarVariant
FlCommentRow.time: get String
FlCommentRow.text: get String
FlCommentRow.reply: get bool
FlCommentRow.replyLabel: get String?
FlCommentRow.status: get FlCommentStatus?
FlCommentRow.statusLabel: get String?
FlCommentRow.actions: get List<FlCommentAction>
FlCommentRow.authorBadge: get String?
FlCommentRow.likeLabel: get String?
FlCommentRow.likes: get String
FlCommentRow.liked: get bool
FlCommentRow.onLike: get VoidCallback?
FlCommentRow.build: Widget(BuildContext context)

Reference renders

The 16 images published with version 0.2.4, rendered by the shared harness on macOS with Flutter 3.47.7. fliin verify comment-row compares your copy to them.

iOS · Light
Comment row, iOS, Light, text 1.0×, Left to right
Text 1.0× · Left to right
Comment row, iOS, Light, text 1.0×, Right to left
Text 1.0× · Right to left
Comment row, iOS, Light, text 2.0×, Left to right
Text 2.0× · Left to right
Comment row, iOS, Light, text 2.0×, Right to left
Text 2.0× · Right to left
iOS · Dark
Comment row, iOS, Dark, text 1.0×, Left to right
Text 1.0× · Left to right
Comment row, iOS, Dark, text 1.0×, Right to left
Text 1.0× · Right to left
Comment row, iOS, Dark, text 2.0×, Left to right
Text 2.0× · Left to right
Comment row, iOS, Dark, text 2.0×, Right to left
Text 2.0× · Right to left
Android · Light
Comment row, Android, Light, text 1.0×, Left to right
Text 1.0× · Left to right
Comment row, Android, Light, text 1.0×, Right to left
Text 1.0× · Right to left
Comment row, Android, Light, text 2.0×, Left to right
Text 2.0× · Left to right
Comment row, Android, Light, text 2.0×, Right to left
Text 2.0× · Right to left
Android · Dark
Comment row, Android, Dark, text 1.0×, Left to right
Text 1.0× · Left to right
Comment row, Android, Dark, text 1.0×, Right to left
Text 1.0× · Right to left
Comment row, Android, Dark, text 2.0×, Left to right
Text 2.0× · Left to right
Comment row, Android, Dark, text 2.0×, Right to left
Text 2.0× · Right to left

Use it to

  • show a single comment under a post with its author and time
  • show replies indented under a comment
  • show that a comment is posting or failed to post
  • offer reply and delete links under a comment
  • give a failed comment a retry action
Not for
  • a message in a private discussion: use a message bubble (message-bubble)
  • a whole post with its images: use a post card (post-card)
States
  • Comment
  • Reply
  • Sending
  • Failed

Arrows to move, Enter to open, Escape to close.