Files
reddit-tweaks-ffe/docs/superpowers/specs/2026-08-26-card-layout-design.md
cottongin cab7c61c07 Update card layout spec after adversarial review
- Move isDarkMode/getColorPrefix to settings.js as shared infra
- Add align-items: flex-start to #siteTable flex container
- Add width: 100% to .rt-card for flex-start compatibility
- Clarify scope: old.reddit.com only

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-26 20:55:08 -04:00

14 KiB
Raw Blame History

Card Layout: shadcn-Style Post Cards — Design Spec

Date: 2026-08-26 Status: Draft

Overview

Replace old Reddit's flat post list with individual cards styled to match shadcn/ui's Card component. Each div.thing.link gets card treatment — background, border, radius, shadow, and internal flexbox layout — using the exact design tokens from shadcn's default neutral theme.

Cards are a separate toggle from badges. Either can be enabled independently.

Scope: old.reddit.com only. New Reddit (www.reddit.com) is out of scope for this extension.

Approach

Class injection + scoped CSS overrides. Each .thing.link receives an rt-card class via JS. All card styling is scoped under .rt-card in CSS. The existing DOM tree stays intact — no element relocation or wrapper injection. The internal float-based layout is overridden to flexbox within the .rt-card scope.

shadcn Reference Values

All values sourced from the shadcn Card registry and theming docs. The Card component classes are:

flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm

Token-to-CSS Mapping (Default Neutral Theme)

Token Light Dark
--card #ffffff (oklch 1 0 0) #171717 (oklch 0.205 0 0)
--card-foreground #0a0a0a (oklch 0.145 0 0) #fafafa (oklch 0.985 0 0)
--border #e5e5e5 (oklch 0.922 0 0) rgba(255,255,255,0.1) (oklch 1 0 0 / 10%)
--muted-foreground #737373 (oklch 0.556 0 0) #a3a3a3 (oklch 0.708 0 0)
--muted #f5f5f5 (oklch 0.97 0 0) #262626 (oklch 0.269 0 0)
--radius 0.625rem (10px) same

Computed CSS Values

Property Tailwind class Value
border-radius rounded-xl 0.875rem (14px) — --radius × 1.4
vertical padding py-6 1.5rem (24px)
horizontal padding px-6 (CardContent) 1.5rem (24px)
section gap gap-6 1.5rem (24px)
shadow shadow-sm 0 1px 2px 0 rgb(0 0 0 / 0.05)

Card Container Styling

shadcn's Card root has py-6 (vertical only); horizontal padding lives on CardContent/CardHeader/CardFooter (px-6). Since we're styling a flat .thing.link without shadcn's section anatomy, we apply padding: 1.5rem on all sides — equivalent to both py-6 and px-6 combined.

Light Mode

.rt-card {
  background: #ffffff;
  color: #0a0a0a;
  border: 1px solid #e5e5e5;
  border-radius: 0.875rem;
  padding: 1.5rem;
  box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
  transition: border-color 0.15s;
}

Dark Mode

.rt-dark .rt-card {
  background: #171717;
  color: #fafafa;
  border-color: rgba(255, 255, 255, 0.1);
}

Hover Effect

Border darkens one step on hover:

.rt-card:hover {
  border-color: #d4d4d4;  /* neutral-300 */
}

.rt-dark .rt-card:hover {
  border-color: rgba(255, 255, 255, 0.18);
}

Shadow does not change on hover — consistent with shadcn's static card.


Internal Layout

Old Reddit uses float-based layout inside .thing.link:

[.rank] [.midcol (votes)] [.thumbnail] [.entry (title + tagline + buttons)]

All children float left; .entry flows after the floats. A .clearleft div clears at the end.

The card overrides this to flexbox:

.rt-card {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  width: 100%;
  overflow: hidden;
}

.rt-card .midcol,
.rt-card .thumbnail {
  float: none;
  flex-shrink: 0;
}

.rt-card .entry {
  flex: 1;
  min-width: 0;
}

.rt-card .clearleft {
  display: none;
}

The .rank element (post number, only visible when enabled in Reddit preferences) participates in the flex flow naturally.

The badges container (.reddit-tweaks-badges) stays at the top of .entry where it's already injected by the badge tweak. No change to badge positioning — badges sit inside the card when both features are enabled.


Settings

Four new settings:

Key Type Default Description
badgeLayout boolean true Enable/disable badge replacements (new toggle for existing feature)
cardLayout boolean true Enable/disable card layout
cardBackground "reddit" | "shadcn" "reddit" Page background style
cardGap number 16 Pixel gap between cards

badgeLayout is a new toggle that controls the existing badge replacement feature. Previously, badges always ran when enabled was on. Now enabled remains the master switch, and badgeLayout/cardLayout are independent sub-toggles beneath it.

The FOUC CSS that hides original subreddit/comment elements is scoped to html.rt-badges-enabled (renamed from html.rt-enabled) so it only applies when badges are active. main.js adds rt-badges-enabled to <html> only when settings.badgeLayout is true.

Page Background Options

  • "reddit" — no background override. Reddit's native background (#f6f7f8 light, #1a1a1b dark) provides natural contrast with the white/dark card surfaces.
  • "shadcn" — override the content area background to shadcn's --muted token (#f5f5f5 light / #262626 dark). Gives cards slightly more visual lift.

Card Gap

The gap between cards is controlled by a CSS custom property --rt-card-gap set on #siteTable. The popup offers preset values: 8px, 12px, 16px (default), 24px, 32px.

Popup UI

A badge toggle is added to the existing "Enabled" section, and a new "Card Layout" section appears between "Dark Mode" and "Light Mode Colors":

┌─────────────────────────────┐
│ [✓] Enabled                 │
│ [✓] Badge layout            │
└─────────────────────────────┘

┌─────────────────────────────┐
│ Card Layout                 │
│                             │
│ [✓] Enable card layout      │
│                             │
│ Background Style            │
│ [Reddit Default ▼]          │
│                             │
│ Card Spacing                │
│ [16px ▼]                    │
└─────────────────────────────┘

Post List Container

The #siteTable (parent of all .thing.link elements) is converted to a flex column so the gap property works:

html.rt-card-enabled #siteTable {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--rt-card-gap, 1rem);
}

align-items: flex-start preserves block-layout behavior — non-post children (pagination links, RES markers, empty-state messages) keep their intrinsic width instead of stretching to fill the container. Posts (.rt-card) are set to full width explicitly.

Non-post children of #siteTable (ads, "next page" links, clearfix divs) participate in the flex flow. Clearfix divs that add unwanted gaps are hidden:

html.rt-card-enabled #siteTable > .clearleft {
  display: none;
}

Page background override:

html.rt-card-bg-shadcn .content[role="main"] {
  background: #f5f5f5;
}

html.rt-card-bg-shadcn.rt-dark .content[role="main"] {
  background: #262626;
}

Architecture

New Files

File Purpose
content/tweaks/cards.js Card tweak module
content/styles/cards.css Card CSS

Modified Files

File Change
manifest.json Add content/styles/cards.css to CSS array, add content/tweaks/cards.js to JS array (before main.js)
lib/settings.js Add badgeLayout, cardLayout, cardBackground, cardGap to DEFAULTS. Move isDarkMode() and getColorPrefix() here from badges.js as shared utilities.
content/main.js Gate badge tweak behind settings.badgeLayout. Call cardTweak.apply(settings) when settings.cardLayout is true. Add rt-badges-enabled class only when badges are on. Wire storage.onChanged to update card settings live.
content/styles/badges.css Change FOUC selectors from html.rt-enabled to html.rt-badges-enabled
popup/popup.html Add "Card Layout" section with toggle, background dropdown, spacing dropdown. Add badge toggle to existing section.
popup/popup.js Wire the new controls to save/load

Module API

content/tweaks/cards.js exposes:

window.RedditTweaks.cardTweak = {
  apply(settings),           // process existing posts + start observer
  updateSettings(settings),  // live-update gap/background without reload
};

apply(settings) Responsibilities

  1. Add rt-card-enabled class to <html>.
  2. Query all div.thing.link, add rt-card class to each.
  3. Set --rt-card-gap custom property on #siteTable from settings.cardGap.
  4. Apply background class (rt-card-bg-shadcn) to <html> if settings.cardBackground === "shadcn".
  5. Apply dark mode class (rt-dark) based on current dark mode state.
  6. Start MutationObserver on #siteTable (or fallback container) to add rt-card to dynamically loaded posts (RES infinite scroll, etc.).

updateSettings(settings) Responsibilities

  1. Update --rt-card-gap custom property.
  2. Toggle rt-card-bg-shadcn class on <html>.
  3. No need to reprocess individual posts — the CSS handles the rest via the scoped classes.

Interaction with Badges

Cards and badges are fully independent:

  • Both enabled: badges sit inside the card (in .entry) as they do today.
  • Cards only: post renders as a card without badge replacements.
  • Badges only: existing behavior unchanged, no card styling.
  • Neither (but enabled is on): no tweaks are applied, original Reddit layout is preserved.

Dark Mode

The card tweak respects the same dark mode setting (auto/light/dark) as badges. The rt-dark class is applied to the <html> element so both card and badge CSS can reference it.

isDarkMode(settings) and getColorPrefix(settings) are moved from badges.js into settings.js as shared infrastructure, exposed as window.RedditTweaks.isDarkMode and window.RedditTweaks.getColorPrefix. Both badges.js, cards.js, and color-popover.js reference these shared functions. This eliminates the card module's dependency on the badge module for dark mode detection.


Edge Cases

  • RES infinite scroll: The MutationObserver catches new .thing.link elements and adds rt-card. Same pattern as the badge tweak's observer.
  • RES night mode toggle: Dark mode observer (already in badges.js) triggers updateSettings() to refresh card dark mode.
  • Promoted posts / ads: Reddit's promoted posts use .thing.promoted.link. They match div.thing.link and get card treatment. If this is undesirable, they can be excluded via .thing.promoted selector, but for now they're included for visual consistency.
  • Self-text expanders: Clicking "expand" on a self-post adds content inside .entry. Since .entry has flex: 1, the card grows to accommodate the expanded content naturally.
  • Thumbnails missing: Some posts have no thumbnail (text posts, or when thumbnails are disabled). The flex layout handles this — .entry fills the available space regardless of whether .thumbnail is present.
  • Non-post elements in #siteTable: The flex column + gap on #siteTable affects all children, including the "next page" nav links. These continue to render normally in the flex flow. Clearfix divs (.clearleft) are hidden.
  • Cards disabled mid-session: Toggling cardLayout off requires a page reload (same pattern as the existing enabled toggle). The storage.onChanged listener detects the change and reloads.
  • Cards enabled but badges disabled: The .reddit-tweaks-badges container is not injected. The card renders the post without badges, showing the original subreddit link and comments link in their default positions (not hidden, since the badge tweak's FOUC CSS is scoped to html.rt-badges-enabled).
  • Popover positioning: The color popover uses position: absolute relative to the badge. Inside a card, the badge's offset parent is still .entry (or the card itself). The popover's existing viewport-aware positioning logic handles this.

Testing

Manual testing checklist:

  1. Enable card layout — each post renders as a card with white background, border, rounded corners, and subtle shadow.
  2. Hover a card — border darkens subtly, no shadow change.
  3. Cards have proper internal layout: vote arrows, thumbnail, and entry are horizontally aligned with 12px gaps.
  4. Long post titles wrap within the card without overflowing.
  5. Posts without thumbnails render correctly (entry fills the card width).
  6. Self-text expansion works — card grows to fit expanded content.
  7. Card gap setting: changing from 16px to 8px/24px/32px updates spacing between cards live.
  8. Background setting "shadcn" changes the page background to #f5f5f5.
  9. Background setting "Reddit Default" uses Reddit's native background.
  10. Dark mode: cards switch to dark surface (#171717), dark border, light text.
  11. Dark mode: shadcn background switches to #262626.
  12. Dark mode hover: border lightens subtly.
  13. RES infinite scroll: new posts loaded via infinite scroll get card treatment.
  14. RES night mode toggle: cards respond to night mode changes.
  15. Cards + badges both enabled: badges appear inside the card at the top of .entry.
  16. Cards enabled, badges disabled: posts render as cards without badge replacements.
  17. Cards disabled, badges enabled: existing badge behavior unchanged, no card styling.
  18. Disable card layout toggle — page reloads and posts return to flat list.
  19. Card spacing dropdown offers 8px, 12px, 16px, 24px, 32px.
  20. Settings persist across browser restart.