Build a Cart with Hydrogen
Previously, you built a product page. Your Hydrogen storefront now renders detailed information about products and provides a "Shop Pay" button to customers. You're now ready to build a cart in your app.
In this tutorial, you'll build a cart that contains the merchandise that a customer wants to buy, and the estimated cost that's associated with the cart.
What you’ll learn
Anchor link to section titled "What you’ll learn"In this tutorial, you’ll learn how to do the following tasks:
- Create a cart route with a Remix action to handle submitted form data
- Build cart components that can be used on a page or in a drawer component
- Update the product details page to include an Add to cart option
Requirements
Anchor link to section titled "Requirements"You’ve built a product page.
Step 1: Create a cart route
Anchor link to section titled "Step 1: Create a cart route"You need a cart instance and cart data to build the UI pieces for your cart. Your first step is to set up a route that handles an "add to cart" form action.
Create a new file app/routes/cart.jsx
and add the following code:
This code includes a Remix action function that handles a form submission to the /cart
route.
For now, the code renders an empty cart page. Open http://localhost:3000/cart in your browser to verify.

Add the cart API functions
Anchor link to section titled "Add the cart API functions"At the bottom of your cart.jsx
page, add the following code:
This code includes the following functions:
cartCreate
creates a cart with line item data using theCREATE_CART_MUTATION
querycartAdd
adds line items to an existing cart using theADD_LINES_MUTATION
querycartRemove
removes line items from a cart using theREMOVE_LINE_ITEMS_MUTATION
query
Each function uses the context.storefront
to make GraphQL queries and mutations. Remix handles reloading the cart data.
These functions are called from the action
function. Since a route can have only one action
function, you need a switch
statement to run the function based on the submitted formAction
input.
Update your loader to replace the // TODO form action
line with the following code:
Your cart action
function is now ready for form submission. These are the expected form inputs:
Input name | Description | Example |
---|---|---|
cartAction |
The function to run. | ADD_TO_CART or REMOVE_FROM_CART |
countryCode |
The buyer's locale as a two-letter country code. | US , CA |
lines |
A string array of JSON data with merchandiseId and quantity . |
[{"merchandiseId":"gid://...0776","quantity":1}] |
Step 2: Create an add to cart button
Anchor link to section titled "Step 2: Create an add to cart button"You're now ready to add products to the cart. Create a new ProductForm
component in products.$handle.jsx
:
Import
useMatches, useFetcher
at the top of your file:Add the product form component. This component takes a
variantId
prop and adds it to thelines
hidden input field:
Render the
ProductForm
component below your Shop Pay button:
You should now be able to add products to your cart. You can add some products to your cart, but you won't be able to see the results until you render the cart data on the page.
Step 3: Create cart components
Anchor link to section titled "Step 3: Create cart components"You should have a cart saved to your session that includes a few products. In this step, you'll render these components on the cart page:
- Cart line items: Displays the product data and enables removal of line items.
- Cart summary: Shows pricing data.
- Cart actions: Renders a "Continue to checkout" button.
You will eventually render these components in two places: the cart page and a drawer component. This step of the guide focuses on the cart page.
Create a new file Cart.jsx
in app/components
. This serves as your single file for all cart-based components.
Render the cart line items
Anchor link to section titled "Render the cart line items"Add the following code to your Cart.jsx
file. This code includes two components:
CartLineItems
takes the GraphQL response of acart.lines
object and flattens thenode
andedge
objects to a simple array.LineItem
renders the cart line item's product image, name, variant data, quantity, and price.
Now you can retrieve the cart data. You need a cart query and a loader function for this route. You'll eventually use this query in two places, so create a new file cart.js
in app/queries
and add the following code:
Then set up your loader function:
Then, update your Cart
function to get the cart
data from the loader. If the cart data doesn't exist or the cart is empty, then render the empty cart block:
Visit http://localhost:3000/cart in your browser. You should be able to see your cart line items.

Setup the ItemRemoveButton
component
Anchor link to section titled "Setup the ItemRemoveButton component"In your Cart.jsx
component file, add the following components:
The ItemRemoveButton
button should look similar to the ProductForm
button. This component uses a fetcher.Form
submission to /cart
with a REMOVE_FROM_CART
action.
Render this component in LineItem
with the lineIds
prop under the quantity line.

Refresh your cart page and test the remove button. You now have control of your cart contents.
Setup summary and checkout actions
Anchor link to section titled "Setup summary and checkout actions"Next, you'll handle summary data and checkout actions.
Add the following components to your Cart.jsx
file:
These components render cart totals and a checkout button.
Import the components into your cart.jsx
file:
Replace your placeholder content <p>TODO Cart Summary</p>
with these components:

Your cart page is now complete. The components that you've built in this step can be reused in the cart drawer that you'll build in the next two steps.
The next two steps focus on the cart drawer, which can be used from any page. To speed up your development, you can use the Headless UI Dialog
component. This enables you to focus on the Remix and cart pieces.
In your terminal, install Headless UI:
Create a new Drawer.jsx
component and paste in the following code:
This code renders a styled Headless UI <Dialog>
element. It also exports a hook with functions to open and close it from any component.
In Layout.jsx
, import the component and its hooks:
In the Layout()
function, set up the useDrawer()
hook:
Below the closing </main>
tag, render your Drawer
:
You don't have a way to see your drawer right now. It renders in the closed state. If you want to test the Drawer, you can run the openDrawer()
function once when the Layout
component mounts. Add this code before the return
statement in app/components/Layout.jsx
:
Reload your page to confirm the drawer renders, and then remove this code block.
Step 4: Add a cart icon to the header
Anchor link to section titled "Step 4: Add a cart icon to the header"In this step, you'll setup a cart icon in your header that opens the drawer.
In
root.jsx
, import theCART_QUERY
that you built for the cart page, and import the Remixdefer
function:Create a function to load cart data:
Update the loader to fetch information about the cart:
In
Layout.jsx
, importAwait
andSuspense
and create aCartHeader
component:In
Layout.jsx
, render the newCartHeader
component:

Now you can open and close the drawer from the header. The drawer will be empty until you render cart components in the drawer.
Step 5: Render cart components in the drawer
Anchor link to section titled "Step 5: Render cart components in the drawer"The cart page and the cart drawer will display the same data. The only difference is the layout.
You can create a new CartDrawer
component in Layout.jsx
.
Import the components at the top of the file:
Add the following code to the bottom of the file:
This uses similar code to the cart components that you created previously. The cart components are rendered with the same data, but uses different wrapping elements.
Render this component inside your Drawer
:

Your cart drawer should now render up-to-date product data as you add and remove products from your cart.
Step 6: Open the drawer on product add
Anchor link to section titled "Step 6: Open the drawer on product add"In this step, you'll learn how to open the drawer when a product is added to the cart. Your app can respond to any fetcher function that's called with the ADD_TO_CART
action.
In Layout.jsx
, import the necessary dependencies:
Then set up the useFetchers
hook:
It's possible to have several fetchers querying or submitting data at once. You need to filter down the active fetches to the "Add to cart" actions. When an "Add to cart" action is found, open the drawer inside a useEffect
hook:

The cart drawer should now open when an add to cart form is submitted. The cart data automatically refreshes when the action finishes and Remix runs its loaders.
This guide shows how to create a functional cart, but you can progressively enhance it with additional features. The following are some examples:
- Add increment and decrement functionality for each line item
- Add optimistic UI for adding, updating, and removing cart line items
- Change a line item's options
- Calculate shipping
- Add a discount code
The demo store
template includes a fully featured cart. You can explore all of these features and build upon what you have learned in this guide.
You can build on the code in this guide as you explore more features of Hydrogen and Remix.