FeedItem
A social-feed item: avatar, name, handle, time, text, a picture or any media, and like, comment and share buttons with counts.
Demo
Nothing yet.
Get it
It needs theme.qwe, as every component does, and avatar.qwe; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.
$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/avatar.qwe$ curl -O https://quillolang.org/components/ui/feed_item.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/feed_item.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/feed_item.qwe
component FeedItemDemoLive() { state liked = false state likes = 128 state last = "Nothing yet." <div.demo-stack> <div style="border: 1px solid var(--ui-border); border-radius: var(--ui-radius); overflow: hidden"> <FeedItem id="feed-item-demo-live" name="Ada Lovelace" handle="@ada" time="2h" datetime="2026-10-28T09:00:00Z" href="#feed_item" verified={true} text="The first program is written.\nThe machine is not even built yet." media="https://picsum.photos/seed/engine/800/400" likes={likes} comments={12} shares={4} liked={liked} on_like={() => { liked = !liked; likes = liked ? likes + 1 : likes - 1; last = liked ? "Liked." : "Unliked." }} on_comment={() => { last = "Comment box opened." }} on_share={() => { last = "Share sheet opened." }}/> <FeedItem id="feed-item-demo-plain" name="Grace Hopper" handle="@grace" src="https://i.pravatar.cc/80?img=47" time="Yesterday" text="A nanosecond is 30 cm of wire. Keep one on your desk." likes={64} comments={3}/> <FeedItem name="Alan Turing" time="Oct 20" text="Can machines think?"> <div style="padding: 12px 14px; font-size: 14px; color: var(--ui-muted)">A quoted post, as children.</div> </FeedItem> </div> <p.demo-note>{last}</p> </div>}view FeedItemDemo() = <div.demo-stack> <FeedItemDemoLive/> </div>Source
ui/feed_item.qwe
// FeedItem: one item of a social feed: who posted (picture, name, handle), when,// what they wrote, a picture or any media, and the like, comment and share counts// as buttons.//// <FeedItem name="Ada Lovelace" handle="@ada" time="2h" datetime="2026-10-28T09:00:00Z"// text="The first program, and the machine is not even built yet." media="/img/engine.jpg"// likes={128} comments={12} shares={4}/>// <FeedItem {...post} liked={post.liked} on_like={() => { like(post.id) }}/>//// name, handle, src: who, with their picture (initials without one). time: when,// as it should read ("2h"); datetime: its ISO form; href: a link to the item on// the time. text: what they wrote (line breaks kept). media: an image URL, or// children for anything else (a video, a quoted post). likes, comments, shares:// the counts; a count left out hides its button. liked: this reader's like, the// heart filled. on_like(), on_comment(), on_share(): inside one of your components,// what each button does. verified: a tick after the name. id: the item's id.// Needs avatar.qwe.import ui/avatar.qwe use Avatarimport ui/theme.qwe use ui_clslet feed_item_css = css`.ui-feed-item { display: grid; grid-template-columns: auto 1fr; gap: 12px; min-width: 0; padding: 14px 16px; border-bottom: 1px solid var(--ui-border); background: var(--ui-surface); color: var(--ui-fg); font: 15px/1.45 var(--ui-font); }.ui-feed-item-body { display: grid; gap: 6px; min-width: 0; }.ui-feed-item-head { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; font-size: 14px; }.ui-feed-item-name { font-weight: 650; }.ui-feed-item-tick { width: 15px; height: 15px; color: var(--ui-info); }.ui-feed-item-handle, .ui-feed-item-head time, .ui-feed-item-head a { color: var(--ui-muted); text-decoration: none; }.ui-feed-item-head a:hover { text-decoration: underline; }.ui-feed-item-head a:focus-visible { outline: none; border-radius: 4px; box-shadow: var(--ui-focus); }.ui-feed-item-head time::before { content: "·"; margin-right: 6px; }.ui-feed-item-text { margin: 0; white-space: pre-line; overflow-wrap: anywhere; }.ui-feed-item-media { margin-top: 4px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); overflow: hidden; }.ui-feed-item-slot:empty { display: none; }.ui-feed-item-media img { display: block; width: 100%; max-height: 420px; object-fit: cover; }.ui-feed-item-acts { display: flex; gap: 4px; margin: 4px 0 0 -8px; }.ui-feed-item-act { display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 10px; border: 0; border-radius: 999px; background: transparent; color: var(--ui-muted); font: 500 13px/1 var(--ui-font); font-variant-numeric: tabular-nums; cursor: pointer; }.ui-feed-item-act:hover { background: var(--ui-subtle); color: var(--ui-fg); }.ui-feed-item-act:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-feed-item-act svg { width: 17px; height: 17px; }.ui-feed-item-like:hover { color: var(--ui-danger); background: var(--ui-danger-soft); }.ui-feed-item-like[aria-pressed="true"] { color: var(--ui-danger); }.ui-feed-item-like[aria-pressed="true"] svg { fill: currentColor; }.ui-feed-item-comment:hover { color: var(--ui-info); background: var(--ui-info-soft); }.ui-feed-item-share:hover { color: var(--ui-success); background: var(--ui-success-soft); }`view FeedItem(name: str, handle: str? = null, src: str? = null, time: str? = null, datetime: str? = null, href: str? = null, text: str = "", media: str? = null, likes: int? = null, comments: int? = null, shares: int? = null, liked: bool = false, verified: bool = false, on_like = null, on_comment = null, on_share = null, id: str? = null, class: str? = null, children) { style global feed_item_css let when = time != null ? <time datetime={datetime}>{time}</time> : null <article class={ui_cls(["ui-feed-item", class])} id={id}> <Avatar name={name} src={src}/> <div.ui-feed-item-body> <div.ui-feed-item-head> <span.ui-feed-item-name>{name}</span> {verified && <svg.ui-feed-item-tick viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 1.5l2.6 2.2 3.4-.4 1 3.3 3 1.7-1.3 3.2 1.3 3.2-3 1.7-1 3.3-3.4-.4L12 22.5l-2.6-2.2-3.4.4-1-3.3-3-1.7 1.3-3.2L2 9.3l3-1.7 1-3.3 3.4.4Zm-1.4 13.2 5.6-5.6-1.4-1.4-4.2 4.2-2.1-2.1-1.4 1.4Z"/></svg>} {verified && <span.ui-sr>, verified</span>} {handle != null && <span.ui-feed-item-handle>{handle}</span>} {when != null && (href != null ? <a href={href}>{when}</a> : when)} </div> {text != "" && <p.ui-feed-item-text>{text}</p>} {media != null && <div.ui-feed-item-media><img src={media} alt="" loading="lazy"/></div>} <div.ui-feed-item-media.ui-feed-item-slot>{children}</div> {(likes != null || comments != null || shares != null) && <div.ui-feed-item-acts> {likes != null && <button.ui-feed-item-act.ui-feed-item-like type="button" aria-pressed={liked ? "true" : "false"} aria-label={`Like, ${likes}`} @click={() => { on_like && on_like() }}> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20.5s-8-4.8-8-11A4.5 4.5 0 0 1 12 7a4.5 4.5 0 0 1 8 2.5c0 6.2-8 11-8 11Z"/></svg> <span aria-hidden="true">{likes}</span> </button>} {comments != null && <button.ui-feed-item-act.ui-feed-item-comment type="button" aria-label={`Comments, ${comments}`} @click={() => { on_comment && on_comment() }}> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12a8 8 0 0 1-11.6 7.1L4 20.5l1.5-4.6A8 8 0 1 1 21 12Z"/></svg> <span aria-hidden="true">{comments}</span> </button>} {shares != null && <button.ui-feed-item-act.ui-feed-item-share type="button" aria-label={`Share, ${shares}`} @click={() => { on_share && on_share() }}> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3l4 4-4 4M21 7H10a5 5 0 0 0-5 5v1M7 21l-4-4 4-4M3 17h11a5 5 0 0 0 5-5v-1"/></svg> <span aria-hidden="true">{shares}</span> </button>} </div>} </div> </article>}Related
ProfileCardA person at a glance: picture, name, title, bio, a row of stats and action buttons, stacked or in a row.7 views · 4 downloadsReviewCardOne review: stars (halves too), title, text, reviewer and date, a Verified mark, images, the seller's reply and a Helpful vote.7 views · 6 downloadsTestimonialCardA customer's quote with their picture, name, title and company or logo, and stars if they gave some.5 views · 6 downloadsContactCardA person's contact details as tel:, mailto: and map links, with a vCard to download in one tap.5 views · 7 downloadsEmailPreviewAn email as a client shows it: subject, labels and star, sender with picture, to and cc, the date, the message and attachments to download.6 views · 7 downloadsIdCardAn ID badge with a lanyard slot: photo or initials, name, role, organisation, number, issued and expiry dates, an Expired stamp, and a barcode slot.5 views · 6 downloads
Thread
Thread 0 posts
On FeedItem: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.