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.
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.
"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-cardCopies 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.jsonWorks, 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
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
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.
| Prop | Type | Default |
|---|---|---|
namerequiredDisplay name of the author. | string | none |
handleHandle of the author, with or without a leading @. | string | none |
avatarSrcAvatar image URL. Without it the initials are shown. | string | none |
timestampTime as text, for example 2h or 12 Mar. Pass the already formatted text. | string | none |
dateTimeMachine readable date for the time element. | string | none |
childrenrequiredThe post body. | node | none |
mediaOptional media slot under the body, such as an image. | node | none |
linkPreviewOptional link preview slot under the media. | node | none |
replyCountNumber of replies. | number | none |
repostCountNumber of reposts. | number | none |
likeCountNumber of likes. | number | none |
isLikedWhether the current user liked the post. | boolean | false |
isRepostedWhether the current user reposted it. | boolean | false |
onReplyCalled when Reply is pressed. The button is only drawn when this is set. | () => void | none |
onRepostCalled when Repost is pressed. The button is only drawn when this is set. | () => void | none |
onLikeCalled when Like is pressed. The button is only drawn when this is set. | () => void | none |
variantCompact uses smaller spacing and text. | "default" | "compact" | "default" |
localeLocale used to format counts. Defaults to the app locale. | string | none |
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