Skip to content

feat(card): scaffold shared CardBase, template#6491

Open
5t3ph wants to merge 5 commits into
mainfrom
seckles/cards-template
Open

feat(card): scaffold shared CardBase, template#6491
5t3ph wants to merge 5 commits into
mainfrom
seckles/cards-template

Conversation

@5t3ph

@5t3ph 5t3ph commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Description

Plans and scaffolds the shared foundation for the swc-card family. See also the related Slack thread and Miro board for visuals and additional discussion.

Important

Review the planning doc firstCONTRIBUTOR-DOCS/03_project-planning/03_components/card/migration-plan.md, covering the React/1st-gen reference comparison, API decisions, styling approach, accessibility decisions, and 8 open items.

Implemented features:

  • CardBase (2nd-gen/packages/core/components/card/) — behavior-only base class: size (via SizedMixin), variant, density, and their dev-mode validation, plus titleAsLink/selectable for whole-card-clickable behavior (click-proxy to a consumer-supplied link, keyboard activation, and filtering that excludes clicks on nested interactive targets — including targets inside another custom element's own shadow root).
  • renderCardTemplate() (2nd-gen/packages/swc/components/card/card-template.ts) — the shared anatomy render function every concrete card will call from its own render(), mirroring the existing renderPendingSpinner() shared-function pattern.
  • card-template.css (swc/stylesheets/_lit-styles/) — placeholder for the shared structural rules; not yet populated.
  • Tests — 15 tests against test-only fixtures in swc/components/card/test/, since no concrete card component exists yet. This is new territory for the codebase (no prior precedent for testing an abstract base via a throwaway concrete subclass) and is documented as such in the plan.

No concrete card component (swc-card, swc-user-card, swc-product-card) is implemented in this PR — that's explicitly out of scope, left to three follow-up tickets.

Motivation and context

Three planned card components share layout and interactive behavior (sizing, variant, density, whole-card-clickable handling). Scaffolding that shared foundation first — base class, shared render function, shared styles placeholder — avoids duplicating it across three upcoming per-component tickets and lets them be built in parallel against a single, already-tested contract.

Related issue(s)

  • SWC-2316

Manual review test cases

  • Review the plan for completeness and open items

    1. Open CONTRIBUTOR-DOCS/03_project-planning/03_components/card/migration-plan.md
    2. Read the TL;DR and Blockers and open questions sections
    3. Expect a clear picture of what's built, what's deferred to per-component tickets, and the 8 open items (naming, styling-token mapping, and design confirmations) — none blocking this PR
  • Run the CardBase test suite

    1. From 2nd-gen/packages/swc, run yarn vitest --run --project storybook card-base
    2. Expect 15/15 tests passing

@5t3ph 5t3ph requested a review from a team as a code owner July 9, 2026 19:18
@5t3ph 5t3ph added Component:Card Status:Ready for review PR ready for review or re-review. 2nd gen These issues or PRs map to our 2nd generation work to modernizing infrastructure. skip_vrt Skip VRT build; mark UI Tests green without running Chromatic labels Jul 9, 2026
@changeset-bot

changeset-bot Bot commented Jul 9, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: b9c8308

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR


| # | Item | Status |
|---|---|---|
| **Q1** | `selectable`: exact dispatched event name (`swc-card-click` proposed) | Open — naming only; `tabindex` management is implemented |

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please leave feedback on the name of this event, keeping in mind that if/when we implement a CardView-like component to enable checkbox selection that it may pick up on this event to check the checkbox (React offers a combined click, and also keeping the card click distinct from the checkbox click).

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

📚 Branch Preview Links

🔍 Gen1 Visual Regression Test Results

When a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:

Deployed to Azure Blob Storage: pr-6491

If the changes are expected, update the current_golden_images_cache hash in the circleci config to accept the new images. Instructions are included in that file.
If the changes are unexpected, you can investigate the cause of the differences and update the code accordingly.

- **Elevate nested interactive targets** — actions, in-content links, etc. need `position: relative` plus a higher stacking order than the stretched pseudo-element/click-catching surface, in `card-template.css`, so native hit-testing routes their own clicks to them rather than the card surface. This is a CSS-only concern, independent of the JS filtering below.
- **Filter clicks on nested interactive targets** — implemented in `CardBase`'s click handler via `event.composedPath()`, checking each node's `tabIndex` IDL property (not the `tabindex` attribute or a tag-name list): natively-interactive elements (`button`, `input`, `select`, `textarea`, `a[href]`) report `tabIndex >= 0` with no explicit attribute needed, `tabindex="-1"` and disabled controls correctly report as non-interactive, and — because `composedPath()` already traverses into other custom elements' shadow roots for composed events like `click` — an internal `<button>` inside e.g. `<swc-button>` slotted into `actions` is inspected directly, regardless of which shadow tree it belongs to. No tag-name enumeration, and no `stopPropagation()` wiring on wrapper elements in `renderCardTemplate()`, is needed. The `actions` slot is additionally excluded unconditionally (checking whether its `<slot>` element appears in the composed path) as defense in depth, since it's contractually for interactive content regardless of whether a given control correctly reflects focusability — mirroring 1st-gen's own belt-and-suspenders approach (a hard `stopPropagationOnHref` boundary for actions, plus a softer anchor-detection heuristic for everything else).

<details>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

View the rich diff to see this Mermaid flowchart

@coveralls

coveralls commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 29048505172

Coverage remained the same at 96.257%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 39169
Covered Lines: 37903
Line Coverage: 96.77%
Relevant Branches: 6460
Covered Branches: 6018
Branch Coverage: 93.16%
Branches in Coverage %: Yes
Coverage Strength: 461.57 hits per line

💛 - Coveralls

TestCardWithMediaExtras,
} from './test-card-base.js';

// No concrete card component (swc-card, swc-user-card, etc.) exists yet, so

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is a little strange, but in an effort to validate the proposed functionality I wanted to go ahead and run some tests, even though this PR is just the scaffold and split from from dev of the swc components. As noted, this will shift to the first available "real" component.


Three concrete SWC components, all extending the already-built `CardBase`:

- **`swc-card`** — regular, collection, and gallery layouts, all driven by slot presence (no explicit layout attribute). Phase 1 handles basic `<img>` content and "cover" fit behavior directly. Once the 2nd-gen `Asset` component ships, documentation updates to recommend it in place of plain `<img>`; no dedicated `swc-asset-card` is planned.

@rubencarvalho rubencarvalho Jul 9, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you help me understand how would the "collection"/"gallery" layouts be triggered?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll update the plan to be explicit but here's the TL;DR:

Collection - populating that slot will display it accordingly, otherwise it will be hidden

Gallery - populated preview slot and absence of all of title, description, action, default, and footer slots, therefore indicating the image/asset is the main content and will therefore fill the space.

* family plan, A11y-3 / Q8.
*/
@property({ type: Boolean, reflect: true })
public selectable = false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I understanding correctly that this property will eventually become private/internal? Or is our plan to support selectable cards without CardView?

Comment on lines +102 to +109
* @todo No `role` is set when `selectable` is true — deferred rather than
* defaulting to `role="button"`, since the eventual `CardView` selection
* model may call for a different role (e.g. `option`/`gridcell`) that
* `role="button"` would be wrong to have committed to today. See the card
* family plan, A11y-3 / Q8.
*/
@property({ type: Boolean, reflect: true })
public selectable = false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've been keeping to the "component has one role rule" this far along, so I like deferring this to see how we want to do this.

| **Q2** | Gallery badge/avatar via `actions`-slot reuse | Carried over from the scaffold ticket, still TBD |
| **Q3** | `swc-product-card` "side" title position | Carried over from the scaffold ticket, still deferred |
| **Q4** | A `selected` property to complement `selectable` — independently useful (visual/state only), not necessarily tied to a future `CardView` | Open — non-blocking, exploratory; may still end up deferred to `CardView` |
| **Q5** | ARIA `role` for `selectable` (e.g. `button` vs. a future `CardView`-driven `option`/`gridcell`) | Open — deferred; not set today so an eventual role choice isn't foreclosed by committing to `role="button"` now |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with deferring this until we have cardview.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2nd gen These issues or PRs map to our 2nd generation work to modernizing infrastructure. Component:Card skip_vrt Skip VRT build; mark UI Tests green without running Chromatic Status:Ready for review PR ready for review or re-review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants