Detent web components
By Shane Logsdon

Commerce & auth

Pricing

Implemented

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

  • hover recorded state; no setter
  • focus-visible recorded state; no setter
  • collapsed recorded state; no setter
  • dark theme recorded state; no setter
  • light theme recorded state; no setter
  • reduced motion recorded state; no setter

Usage

Import

import '/components/pricing/define.js';

// or register it yourself
import { DetentPricing } from '/components/pricing/pricing.js';

Attributes

  • eyebrow
  • heading
  • reverse
  • subheading

Properties

  • columns
  • freeTier
  • proTier

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.tsx sha256 8baa3d1daba3…

Native files

The native module and its artifacts:

  • src/components/pricing/pricing.ts
  • src/components/pricing/styles.css
  • src/components/pricing/define.ts
  • docs/components/pricing.md
  • site/examples/pricing.ts
  • tests/browser/pricing.spec.ts
  • evidence/components/pricing.md

API

Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.

Attributes

Attributes
AttributePropertyTypeDefaultNotes
eyebroweyebrowstringundefinedKebab-case attribute; camelCase property.
headingheadingstringundefinedKebab-case attribute; camelCase property.
reversereversebooleanfalsePresence semantics: present means true, absent means false.
subheadingsubheadingstringundefinedKebab-case attribute; camelCase property.

Properties

Properties
PropertyTypeDefaultNotes
columns——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
freeTierPricingTierDetent defaultsProperty-only: assign from script; not accepted as an attribute.
proTierPricingTierDetent defaultsProperty-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).
Previous: PressButton Next: Product Tour

Docs

Components

Project