Data & tables
Comparison Table
Interactive tier comparison matrix with difference filtering
detent-comparison-table
Port status
- Catalog status
- implemented
- Origin
- upstream
- Phase
- 3
- Wave
- phase-3-data
- Group
- Data & tables
6.4 KB gzip for the class module
Variants
Three plans with the keyword search, the Differences only filter, collapsible categories, per-feature info tooltips and readable Included / Not included cells. Below 40rem the columns collapse into a segmented plan switcher instead of a scrolling table. Prices and entitlements are static demo data: the CTA is a plain button and no checkout, payment or network call happens.
Ready.
Recorded states
collapsedrecorded state; no setter
Usage
Import
import '/components/comparison-table/define.js';
// or register it yourself
import { DetentComparisonTable } from '/components/comparison-table/comparison-table.js';
Attributes
default-plan-idenable-diff-filterenable-search
Properties
categoriesplans
Slots
No slots are recorded for this component.
Events
No events are recorded for this component.
Example
Runnable example: site/examples/comparison-table.ts. The snippet below is generated from the catalogued attributes.
<detent-comparison-table
default-plan-id
enable-diff-filter
enable-search
></detent-comparison-table>
Property-only values
const element = document.querySelector('detent-comparison-table');
if (element !== null) {
// plans: ComparisonPlan[]
element.plans = [];
// categories: ComparisonCategory[]
element.categories = [];
}
Source
The pinned upstream source for this row:
src/components/ui/SmartComparison.tsxsha256 9783ffc73a23…
Native files
The native module and its artifacts:
src/components/comparison-table/comparison-table.tssrc/components/comparison-table/styles.csssrc/components/comparison-table/define.tsdocs/components/comparison-table.mdsite/examples/comparison-table.tstests/browser/comparison-table.spec.tsevidence/components/comparison-table.md
API
Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.
Attributes
| Attribute | Property | Type | Default | Notes |
|---|---|---|---|---|
default-plan-id | defaultPlanId | string | featured plan or plans[0].id | Kebab-case attribute; camelCase property. |
enable-diff-filter | enableDiffFilter | boolean | true | Presence semantics: present means true, absent means false. |
enable-search | enableSearch | boolean | true | Presence semantics: present means true, absent means false. |
Properties
| Property | Type | Default | Notes |
|---|---|---|---|
categories | ComparisonCategory[] | [] | Property-only: assign from script; not accepted as an attribute. |
plans | ComparisonPlan[] | [] | Property-only: assign from script; not accepted as an attribute. |
Events
No custom events are recorded for this component.
Slots
No slots are recorded for this component.
CSS customisation
- `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
- Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
- `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- A React.ReactNode cell value cannot cross into a custom element, so cell values are string | number | boolean | null; rich cell content would need a per-cell slot, which is not this row's recorded API (R-1.2, PC-17). Custom rendering is a CSS parts + data-* concern instead.
- plan.onCtaClick is not ported as a property: it is a function inside plain data (PC-3.1, PC-6.3). The CTA is a native <button part="cta"> whose composed click the consumer can delegate; no custom event is added, keeping the row's events: [].
- Boolean cells show words instead of a ✓/✗ glyph pair, and every plan row is prefixed with the plan name on small screens: recorded accessibility corrections (PC-12.6).
- The info tooltip opens on click/keyboard and closes on Escape; upstream is a hover-only popover (which is unusable on touch). The description is also exposed via aria-describedby, so it is reachable without opening the popover.
- Framer Motion's springGentle height animation and the layoutId mobile pill become a CSS keyframe on the panel and CSS transitions on the switcher; the animated properties are colours and a short fade, not values the shared spring helper can drive. Under reduced motion the animation is removed (PC-10.5).
- The matrix is not dark-only: upstream's hard-coded #0E0E0E/#FAFAFA palette becomes --detent-color-* tokens (PC-9.5).
- className is replaced by the documented parts and custom properties; there is no Tailwind in consumers.
What it does
A tier comparison matrix: plan columns with price and CTA, a keyword search, a "Differences only" toggle, collapsible category accordions of feature rows, per-feature info tooltips, and a segmented plan switcher for small screens instead of a horizontally scrolling table.
Upstream behaviour
- Live "Differences Only" filter to instantly surface plan divergence
- Integrated feature keyword search with real-time row matching
- Collapsible category groups with spring height transitions
- Segmented mobile plan selector avoiding wide horizontal table scrolling
- Contextual info tooltips and custom value cell rendering
Differences from upstream
- `className` prop is replaced by documented CSS parts/custom properties; no Tailwind in consumers.
- Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
- `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- A React.ReactNode cell value cannot cross into a custom element, so cell values are string | number | boolean | null; rich cell content would need a per-cell slot, which is not this row's recorded API (R-1.2, PC-17). Custom rendering is a CSS parts + data-* concern instead.
- plan.onCtaClick is not ported as a property: it is a function inside plain data (PC-3.1, PC-6.3). The CTA is a native <button part="cta"> whose composed click the consumer can delegate; no custom event is added, keeping the row's events: [].
- Boolean cells show words instead of a ✓/✗ glyph pair, and every plan row is prefixed with the plan name on small screens: recorded accessibility corrections (PC-12.6).
- The info tooltip opens on click/keyboard and closes on Escape; upstream is a hover-only popover (which is unusable on touch). The description is also exposed via aria-describedby, so it is reachable without opening the popover.
- Framer Motion's springGentle height animation and the layoutId mobile pill become a CSS keyframe on the panel and CSS transitions on the switcher; the animated properties are colours and a short fade, not values the shared spring helper can drive. Under reduced motion the animation is removed (PC-10.5).
- The matrix is not dark-only: upstream's hard-coded #0E0E0E/#FAFAFA palette becomes --detent-color-* tokens (PC-9.5).
- className is replaced by the documented parts and custom properties; there is no Tailwind in consumers.