Skip to content

FRD-058: Cart Summary Widget

FieldValue
OwnerDavid Holmes
StatusDraft
Last Updated2026-05-26
Target Releasev2.0.0 (P2)
T-Shirt SizeS
TypeWidget

Document Summary

A self-contained cart summary widget that displays line items, calculates subtotals, accepts promotional codes, and provides a checkout call-to-action. The widget is headless with respect to cart state management, accepting items and callbacks via props so consumers own their data layer.


Introduction

Overview

E-commerce and SaaS checkout flows need a consistent, accessible cart summary panel. This widget encapsulates the presentation of cart contents, pricing math, promo-code entry, and the primary checkout action into a single composable unit.

Goals

  • Provide a drop-in cart summary panel that works with any cart state manager.
  • Support line-item display with quantity, unit price, and line total.
  • Accept and validate promo codes via a consumer-supplied callback.
  • Render a configurable checkout CTA button.
  • Ship with Storybook stories covering empty, single-item, multi-item, and promo-applied states.

Non-Goals

  • Cart state management or persistence (consumer responsibility).
  • Payment processing or payment-form integration.
  • Inventory validation or stock-level display.
  • Multi-currency conversion (consumer passes pre-formatted strings).

Scope

In Scope

ItemDescription
CartSummary componentMain widget rendering line items, subtotal, discount, and total
CartLineItem typeData shape for each item in the cart
Promo-code inputText input with apply button; async validation via onApplyPromo callback
Checkout CTAConfigurable button label and onCheckout callback
Empty stateMessage shown when items array is empty
Storybook storiesEmpty, single item, multiple items, promo applied, loading states

Out of Scope

ItemRationale
Cart persistenceConsumer owns local/session storage or API state
Shipping calculationDepends on address; out of widget scope
Tax computationJurisdiction-specific; consumer supplies tax amount
Saved-for-later listSeparate widget concern

Users and Pain Points

UserPain Point
SaaS product teamsRebuilding cart summaries per project with inconsistent accessibility and styling
Design-system consumersNo reusable cart pattern exists; teams copy-paste from checkout-form widget
End users (shoppers)Inconsistent cart experiences across product surfaces

Definitions

TermDefinition
Line itemA single product or service entry in the cart with quantity and price
Promo codeA string token that maps to a discount applied server-side
SubtotalSum of all line-item totals before discount and tax
CTACall to action; the primary button driving the user to the next step

Current State

No dedicated cart summary widget exists. The checkout-form.tsx widget includes inline cart rendering, but it is tightly coupled to the form’s payment flow and cannot be used standalone. Teams duplicate cart display logic across projects.


Proposed Solution

Create a CartSummary widget at src/components/widgets/cart-summary.tsx that:

  1. Accepts an array of CartLineItem objects and renders them in a list.
  2. Computes and displays subtotal, optional discount, optional tax, and total.
  3. Includes an inline promo-code input that calls onApplyPromo(code: string) => Promise<PromoResult>.
  4. Renders a checkout CTA button wired to onCheckout.
  5. Handles empty state with a configurable message.
  6. Uses existing design-system primitives (Button, Input, Badge, Spinner).

Requirements

The widget must be fully controlled via props, perform no side effects beyond invoking callbacks, and render correctly in both light and dark themes.


Functional Requirements

IDRequirementPriority
FR-01Render a list of line items showing name, quantity, unit price, and line totalMust
FR-02Display subtotal as sum of all line totalsMust
FR-03Accept optional discount and tax amounts and reflect them in the totalMust
FR-04Provide a promo-code text input with an “Apply” buttonMust
FR-05Call onApplyPromo on submit; show spinner during async validationMust
FR-06Display promo validation result (success label or error message)Must
FR-07Render a checkout CTA button; call onCheckout on clickMust
FR-08Show empty state when items is an empty arrayMust
FR-09Support a loading prop that disables interactions and shows skeleton placeholdersShould
FR-10Allow item removal via optional onRemoveItem(itemId) callbackShould

Non-Functional Requirements

IDRequirement
NFR-01Bundle size under 5 KB gzipped (excluding shared primitives)
NFR-02Render 50 line items without perceptible jank (<16 ms paint)
NFR-03Full light/dark theme support via design tokens

API / Interface Requirements

interface CartLineItem {
id: string;
name: string;
quantity: number;
unitPrice: number;
imageUrl?: string;
}
interface PromoResult {
valid: boolean;
label?: string; // e.g. "20% off"
discountAmount?: number;
errorMessage?: string;
}
interface CartSummaryProps {
items: CartLineItem[];
currency?: string; // default "USD"
discount?: number;
tax?: number;
loading?: boolean;
promoCode?: string;
checkoutLabel?: string; // default "Checkout"
emptyMessage?: string;
onApplyPromo?: (code: string) => Promise<PromoResult>;
onRemoveItem?: (itemId: string) => void;
onCheckout?: () => void;
className?: string;
}

Accessibility Requirements

IDRequirement
A11Y-01Line-item list uses &lt;ul&gt; with role="list" for screen readers
A11Y-02Promo input has a visible label or aria-label
A11Y-03Checkout CTA is focusable and keyboard-activatable
A11Y-04Promo validation result is announced via aria-live="polite"
A11Y-05Loading state conveys aria-busy="true" on the container
A11Y-06Remove-item buttons include aria-label with item name

Content and Documentation Requirements

  • Storybook doc page explaining props, usage, and composition with checkout flows.
  • Stories: Empty, SingleItem, MultipleItems, WithPromo, PromoError, Loading.
  • JSDoc on all exported types and the main component.

Dependencies

DependencyTypeNotes
ButtonInternalCTA and apply buttons
InputInternalPromo-code field
BadgeInternalDiscount label
SpinnerInternalLoading and async states

Risks and Tradeoffs

RiskImpactMitigation
Currency formatting varies by localeIncorrect display for non-USD usersAccept a formatPrice prop or default to Intl.NumberFormat
Promo validation latencyUser confusion during slow networkShow spinner on apply button; disable checkout during validation
Scope creep toward full checkoutWidget becomes unwieldyEnforce boundary: no payment/address/shipping fields

Open Questions

  1. Should the widget support editable quantities inline, or is that the consumer’s responsibility?
  2. Should line-item images be rendered by default or opt-in via a showImages prop?
  3. Do we need a compact/mini variant for sidebar placement?

Acceptance Criteria

  • CartSummary renders line items with correct subtotal, discount, tax, and total.
  • Promo-code input triggers onApplyPromo and displays result.
  • Checkout CTA calls onCheckout.
  • Empty state renders when no items are provided.
  • All Storybook stories render without errors.
  • Component passes axe accessibility audit with zero violations.
  • Unit tests cover empty, single-item, multi-item, promo-success, and promo-error states.

LLM Handoff Instructions

When implementing this FRD:

  1. Create src/components/widgets/cart-summary.tsx with the types and component.
  2. Create src/components/widgets/cart-summary.stories.tsx with all listed stories.
  3. Create src/components/widgets/cart-summary.test.tsx covering acceptance criteria.
  4. Use Intl.NumberFormat for default currency formatting; allow override via formatPrice prop.
  5. Follow existing widget patterns (see checkout-form.tsx, order-status.tsx for conventions).
  6. Use cn() from @/lib/utils for class merging. No inline styles.
  7. Export all types and the component from the file.

Decision Log

DateDecisionRationale
2026-05-26Widget is presentation-only; no state managementKeeps the widget portable across state managers (Zustand, TanStack Query, etc.)
2026-05-26Promo validation is async via callbackAllows server-side validation without coupling to any API client

Document History

DateVersionAuthorChanges
2026-05-260.1David HolmesInitial draft