Skip to content

Social Post Card

A generic social post: avatar, display name, handle, time text, body, an optional media slot and link preview slot, and a row of real buttons (reply, repost, like) with formatted counts. It is an article with no network branding.

Motionv0.1.0experimentalWCAG 2.2 AAView spec

Optional, and kept apart.

Nothing here is included in a project unless you add it, and the plain component it builds on is unchanged. This effect is decoration: it is hidden from screen readers, stands still for visitors who prefer reduced motion, and can be paused. Use Still on a preview to see that version.

Ada Example@ada
Shipped the first version of the new settings page today. Keyboard users can reach every control.
"use client";

import { useState } from "react";
import { SocialPostCard } from "@rdloom/react";

export default function SocialPostCardBasicExample() {
  const [liked, setLiked] = useState(false);
  return (
    <div className="flex w-full justify-center">
      <SocialPostCard
        name="Ada Example"
        handle="ada"
        timestamp="2h"
        dateTime="2026-03-12T10:00:00Z"
        replyCount={12}
        repostCount={1840}
        likeCount={liked ? 12346 : 12345}
        isLiked={liked}
        onReply={() => undefined}
        onRepost={() => undefined}
        onLike={() => setLiked((v) => !v)}
      >
        Shipped the first version of the new settings page today. Keyboard users can reach every control.
      </SocialPostCard>
    </div>
  );
}

Installation

npx rdloom add social-post-card

Copies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes. It needs react-aria-components; add --install to install them.

Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/social-post-card.json

Works, but without upgrade tracking.

Usage

import { SocialPostCard } from "@rdloom/react";

<div className="flex w-full justify-center">
  <SocialPostCard
    name="Ada Example"
    handle="ada"
    timestamp="2h"
    dateTime="2026-03-12T10:00:00Z"
    replyCount={12}
    repostCount={1840}
    likeCount={liked ? 12346 : 12345}
    isLiked={liked}
    onReply={() => undefined}
    onRepost={() => undefined}
    onLike={() => setLiked((v) => !v)}
  >
    Shipped the first version of the new settings page today. Keyboard users can reach every control.
  </SocialPostCard>
</div>

With media

Grace Sample@grace
Notes from the design review, with the key decisions in the link below.

Notes from the design review

example.test/notes

import { SocialPostCard } from "@rdloom/react";

export default function SocialPostCardWithMediaExample() {
  return (
    <div className="flex w-full justify-center">
      <SocialPostCard
        name="Grace Sample"
        handle="@grace"
        timestamp="12 Mar"
        replyCount={3}
        likeCount={87}
        onReply={() => undefined}
        onLike={() => undefined}
        media={
          <div
            role="img"
            aria-label="Soft gradient placeholder"
            className="h-40 w-full"
            style={{ background: "linear-gradient(135deg, color-mix(in srgb, var(--rd-color-action-primary) 35%, transparent), transparent)" }}
          />
        }
        linkPreview={
          <div className="p-3 text-sm">
            <p className="m-0 font-medium">Notes from the design review</p>
            <p className="m-0 text-[var(--rd-color-text-muted)]">example.test/notes</p>
          </div>
        }
      >
        Notes from the design review, with the key decisions in the link below.
      </SocialPostCard>
    </div>
  );
}

Compact

Lin Tester@lin
Great talk, the demo at the end was the best part.
Sam Placeholder@sam
Agreed. Slides are linked in the event page.
import { SocialPostCard } from "@rdloom/react";

export default function SocialPostCardCompactExample() {
  return (
    <div className="flex w-full flex-col items-center gap-3">
      <SocialPostCard variant="compact" name="Lin Tester" handle="lin" timestamp="5m" likeCount={9} onLike={() => undefined}>
        Great talk, the demo at the end was the best part.
      </SocialPostCard>
      <SocialPostCard variant="compact" name="Sam Placeholder" handle="sam" timestamp="1h" replyCount={1} onReply={() => undefined}>
        Agreed. Slides are linked in the event page.
      </SocialPostCard>
    </div>
  );
}

API Reference

Defined by the spec. Components also accept the props of the React Aria component they wrap.

PropTypeDefault
namerequired

Display name of the author.

stringnone
handle

Handle of the author, with or without a leading @.

stringnone
avatarSrc

Avatar image URL. Without it the initials are shown.

stringnone
timestamp

Time as text, for example 2h or 12 Mar. Pass the already formatted text.

stringnone
dateTime

Machine readable date for the time element.

stringnone
childrenrequired

The post body.

nodenone
media

Optional media slot under the body, such as an image.

nodenone
linkPreview

Optional link preview slot under the media.

nodenone
replyCount

Number of replies.

numbernone
repostCount

Number of reposts.

numbernone
likeCount

Number of likes.

numbernone
isLiked

Whether the current user liked the post.

booleanfalse
isReposted

Whether the current user reposted it.

booleanfalse
onReply

Called when Reply is pressed. The button is only drawn when this is set.

() => voidnone
onRepost

Called when Repost is pressed. The button is only drawn when this is set.

() => voidnone
onLike

Called when Like is pressed. The button is only drawn when this is set.

() => voidnone
variant

Compact uses smaller spacing and text.

"default" | "compact""default"
locale

Locale used to format counts. Defaults to the app locale.

stringnone

Accessibility

Role article, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.

Keyboard

  • Tab: moves to each action button in order
  • Enter or Space: presses the focused button

Screen readers announce

  • Article with the author name, then body, media, then the action buttons with counts

What your code must do

  • The card is an article named by the author name through aria-labelledby
  • Reply, Repost and Like are real buttons with names like Reply, 12 replies; Repost and Like use aria-pressed
  • Counts are formatted with the default locale and compact notation, and the full number is in the accessible name
  • The avatar is decorative when the name is next to it; the time uses a time element
  • Media and link preview are slots: give images their own alt text
  • Static: no motion

Guidelines

Use it when

  • Showing a post, quote or testimonial from a feed
  • Wall of reactions on a landing page

Avoid it when

  • Long conversations that need threading and moderation
  • Anything that needs live updating counts without an announcement

Design tokens

The semantic tokens this component uses. Change them once and every component follows; see Design tokens.

  • --rd-color-surface-default
  • --rd-color-border-default
  • --rd-color-text-default
  • --rd-color-text-muted
  • --rd-color-focus-ring
  • --rd-color-action-primary
  • --rd-radius-overlay