Commerce & auth
Pricing
Minimal two-tier pricing that belongs in Detent
detent-pricing
Port status
- Catalog status
- implemented
- Origin
- upstream
- Phase
- 3
- Wave
- phase-3-data
- Group
- Commerce & auth
6.5 KB gzip for the class module · inlines the shared spring
Variants
Sample data; nothing is fetched or sent. The main preview renders the pinned default tier data; the section above uses property-only tier objects and the reversed layout (Pro on the left). Hover the Pro call to action for the 1.6 s shimmer and the 1.015 scale lift, and resize below 768 px to see the two cards collapse into one column. With Reduce Motion on, the entrance, the lift and the shimmer are gone while every final state stays usable.
Recorded states
hoverrecorded state; no setterfocus-visiblerecorded state; no settercollapsedrecorded state; no setterdark themerecorded state; no setterlight themerecorded state; no setterreduced motionrecorded state; no setter
Usage
Import
import '/components/pricing/define.js';
// or register it yourself
import { DetentPricing } from '/components/pricing/pricing.js';
Attributes
eyebrowheadingreversesubheading
Properties
columnsfreeTierproTier
Slots
No slots are recorded for this component.
Events
No events are recorded for this component.
Example
Runnable example: site/examples/pricing.ts. The snippet below is generated from the catalogued attributes.
<detent-pricing
eyebrow
heading
reverse
subheading
></detent-pricing>
Property-only values
const element = document.querySelector('detent-pricing');
if (element !== null) {
// freeTier: PricingTier
element.freeTier = undefined;
// proTier: PricingTier
element.proTier = undefined;
}
Source
The pinned upstream source for this row:
src/components/ui/Pricing.tsxsha256 8baa3d1daba3…
Native files
The native module and its artifacts:
src/components/pricing/pricing.tssrc/components/pricing/styles.csssrc/components/pricing/define.tsdocs/components/pricing.mdsite/examples/pricing.tstests/browser/pricing.spec.tsevidence/components/pricing.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 |
|---|---|---|---|---|
eyebrow | eyebrow | string | undefined | Kebab-case attribute; camelCase property. |
heading | heading | string | undefined | Kebab-case attribute; camelCase property. |
reverse | reverse | boolean | false | Presence semantics: present means true, absent means false. |
subheading | subheading | string | undefined | Kebab-case attribute; camelCase property. |
Properties
| Property | Type | Default | Notes |
|---|---|---|---|
columns | — | — | Recorded in the catalog without a top-level type; pinned from source when the row is ported. |
freeTier | PricingTier | Detent defaults | Property-only: assign from script; not accepted as an attribute. |
proTier | PricingTier | Detent defaults | 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.
- columns is property-only: the row records no columns attribute, so it is a deliberate PC-3.1 narrowing; 1 column below 48rem, 2 at or above.
- featured, tier and reduceMotion are PricingCard params in the pinned source, not root props: featured is derived from reverse and reduced motion comes from prefers-reduced-motion.
- The shimmer keyframes live in the component stylesheet instead of an injected <style> block (PC-11.3).
- The featured card starts its entrance 50 ms after the other, as upstream's delay: featured ? 0.05 : 0, on a timer cancelled with the springs on disconnect.
- The entrance uses the shared spring helper at the pinned springSmooth constants (200/28/0.85); easing is not substituted (PC-10.4).
- A tier's onCtaClick stays a plain-data hook, so no event is dispatched and CTA activation is the native composed click of the part="cta" button.
- The dark scheme comes from prefers-color-scheme rather than the upstream .dark ancestor class, which cannot reach a shadow tree.
- className is replaced by documented CSS parts and custom properties; the feature icon is a local inline SVG.
- A feature's optional description lives inside the feature part rather than in a separate part.
- Default demo copy and labels use Detent branding instead of EasyUI (pre-release rebrand).
What it does
A two-tier pricing section: a Free and a Pro card with a semantic feature list, the Pro card's elevated surface, the Pro CTA's gradient sheen and one-pass shimmer, an optional caption block, a documented columns control and a light/dark palette.
Upstream behaviour
- Two side-by-side pricing cards (Free + Pro) that collapse to a single column on mobile
- Strictly uses EasyUI design tokens — no hardcoded theme colors, automatic light / dark adaptation
- Typography and hierarchy communicate the tier difference; no badges, countdowns, or "best value" stamps
- Pro card receives a subtle surface elevation in light mode and a brighter surface in dark mode
- Pro CTA uses an animated gradient base that drifts on hover (3.6s ease-in-out, background-position slide) plus a one-pass shimmer beam (1.6s) and a subtle scale lift (1.015) — same EasyUI gradient vocabulary as the existing Button.tsx "gradient" variant
- Free CTA mirrors the secondary/outline button tone for clear hierarchy
- Subtle border-color hover, spring enter animation, and focus-visible ring
- Respects prefers-reduced-motion by disabling non-essential motion
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.
- columns is property-only: the row records no columns attribute, so it is a deliberate PC-3.1 narrowing; 1 column below 48rem, 2 at or above.
- featured, tier and reduceMotion are PricingCard params in the pinned source, not root props: featured is derived from reverse and reduced motion comes from prefers-reduced-motion.
- The shimmer keyframes live in the component stylesheet instead of an injected <style> block (PC-11.3).
- The featured card starts its entrance 50 ms after the other, as upstream's delay: featured ? 0.05 : 0, on a timer cancelled with the springs on disconnect.
- The entrance uses the shared spring helper at the pinned springSmooth constants (200/28/0.85); easing is not substituted (PC-10.4).
- A tier's onCtaClick stays a plain-data hook, so no event is dispatched and CTA activation is the native composed click of the part="cta" button.
- The dark scheme comes from prefers-color-scheme rather than the upstream .dark ancestor class, which cannot reach a shadow tree.
- className is replaced by documented CSS parts and custom properties; the feature icon is a local inline SVG.
- A feature's optional description lives inside the feature part rather than in a separate part.
- Default demo copy and labels use Detent branding instead of EasyUI (pre-release rebrand).