--- title: Bundles in Hydrogen description: Display product bundles on your Hydrogen storefront. source_url: html: 'https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles' md: 'https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md' --- ExpandOn this page * [Requirements](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#requirements) * [Ingredients](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#ingredients) * [Step 1: Set up the Shopify Bundles app](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#step-1-set-up-the-shopify-bundles-app) * [Step 2: Create the Bundle​Badge component](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#step-2-create-the-bundlebadge-component) * [Step 3: Create a new Bundled​Variants component](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#step-3-create-a-new-bundledvariants-component) * [Step 4: Query bundle pricing for recommended products](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#step-4-query-bundle-pricing-for-recommended-products) * [Step 5: Show bundled products on the product page](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#step-5-show-bundled-products-on-the-product-page) * [Step 6: Detect bundles in collection listings](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#step-6-detect-bundles-in-collection-listings) * [Step 7: Identify bundles in the cart](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#step-7-identify-bundles-in-the-cart) * [Step 8: Show bundle badges in the cart](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#step-8-show-bundle-badges-in-the-cart) * [Step 9: Update the cart button text for bundles](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#step-9-update-the-cart-button-text-for-bundles) * [Step 10: Show bundle badges on product images](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#step-10-show-bundle-badges-on-product-images) * [Step 11: Show bundle badges on product cards](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#step-11-show-bundle-badges-on-product-cards) * [Step 12: Position bundle badges on images](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#step-12-position-bundle-badges-on-images) * [Next steps](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#next-steps) # Bundles in Hydrogen This recipe adds special styling for product bundles on your Hydrogen storefront. Customers will see badges and relevant cover images for bundles when they're viewing product and collection pages. In this recipe you'll make the following changes: 1. Set up the Shopify Bundles app in your Shopify admin and create a new product bundle. 2. Update the GraphQL fragments to query for bundles to identify bundled products. 3. Update the product and collection templates to display badges on product listings, update the copy for the cart buttons, and display bundle-specific information on product and collection pages. 4. Update the cart line item template to display the bundle badge as needed. *** ## Requirements To use product bundles, you need to install a bundles app in your Shopify admin. In this recipe, we'll use the [Shopify Bundles app](https://apps.shopify.com/shopify-bundles). *** ## Ingredients *New files added to the template by this recipe.* | File | Description | | - | - | | [app/components/BundleBadge.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/bundles/ingredients/templates/skeleton/app/components/BundleBadge.tsx) | A badge displayed on bundle product listings. | | [app/components/BundledVariants.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/bundles/ingredients/templates/skeleton/app/components/BundledVariants.tsx) | A component that wraps the variants of a bundle product in a single product listing. | *** ## Step 1: Set up the Shopify Bundles app 1. Install the [Shopify Bundles app](https://apps.shopify.com/shopify-bundles) in your Shopify admin. 2. Make sure your store meets the [eligibility requirements](https://help.shopify.com/en/manual/products/bundles/eligibility-and-considerations). 3. From the [**Bundles**](https://admin.shopify.com/apps/shopify-bundles/app) page, [create a new bundle](https://help.shopify.com/en/manual/products/bundles/shopify-bundles). *** ## Step 2: Create the Bundle​Badge component Create a new BundleBadge component to be displayed on bundle product listings. #### File: [BundleBadge.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/bundles/ingredients/templates/skeleton/app/components/BundleBadge.tsx) ## File ```tsx export function BundleBadge() { return (
BUNDLE
); } ``` *** ## Step 3: Create a new Bundled​Variants component Create a new `BundledVariants` component that wraps the variants of a bundle product in a single product listing. #### File: [BundledVariants.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/bundles/ingredients/templates/skeleton/app/components/BundledVariants.tsx) ## File ```tsx import {Link} from 'react-router'; import {Image} from '@shopify/hydrogen'; import type { ProductVariantComponent, Image as ShopifyImage, } from '@shopify/hydrogen/storefront-api-types'; export function BundledVariants({ variants, }: { variants: ProductVariantComponent[]; }) { return (
{variants ?.map(({productVariant: bundledVariant, quantity}) => { const url = `/products/${bundledVariant.product.handle}`; return ( {bundledVariant.title}
{bundledVariant.product.title} {bundledVariant.title !== 'Default Title' ? `- ${bundledVariant.title}` : null} Qty: {quantity}
); }) .filter(Boolean)}
); } ``` *** ## Step 4: Query bundle pricing for recommended products Add `maxVariantPrice` to the `RecommendedProducts` query's product fields. #### File: [\_index.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/app/routes/_index.tsx) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/bundles/patches/_index.tsx.243e26.patch)) ```diff @@ -151,6 +151,10 @@ const RECOMMENDED_PRODUCTS_QUERY = `#graphql amount currencyCode } + maxVariantPrice { + amount + currencyCode + } } featuredImage { id ``` *** ## Step 5: Show bundled products on the product page 1. Add the `requiresComponents` field to the `Product` fragment, which is used to identify bundled products. 2. Pass the `isBundle` flag to the `ProductImage` component. #### File: [products.$handle.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/app/routes/products.$handle.tsx) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/bundles/patches/products.$handle.tsx.6f2e82.patch)) ## File ```diff @@ -15,6 +15,8 @@ import {ProductPrice} from '~/components/ProductPrice'; import {ProductImage} from '~/components/ProductImage'; import {ProductForm} from '~/components/ProductForm'; import {redirectIfHandleIsLocalized} from '~/lib/redirect'; +import type {ProductVariantComponent} from '@shopify/hydrogen/storefront-api-types'; +import {BundledVariants} from '~/components/BundledVariants'; export const meta: Route.MetaFunction = ({data}) => { return [ @@ -104,9 +106,12 @@ export default function Product() { const {title, descriptionHtml} = product; + const isBundle = Boolean(product.isBundle?.requiresComponents); + const bundledVariants = isBundle ? product.isBundle?.components.nodes : null; + return (
- +

{title}



@@ -126,6 +132,14 @@ export default function Product() {

+ {isBundle && ( +
+

Bundled Products

+ +
+ )}
; @@ -24,6 +25,7 @@ export function CartLineItem({ const {product, title, image, selectedOptions} = merchandise; const lineItemUrl = useVariantUrl(product.handle, selectedOptions); const {close} = useAside(); + const isBundle = Boolean(line.merchandise.requiresComponents); return (
  • @@ -38,8 +40,9 @@ export function CartLineItem({ /> )} -
    +
    { @@ -48,9 +51,10 @@ export function CartLineItem({ } }} > -

    +

    {product.title}

    + {isBundle ? : null}
      ``` *** ## Step 9: Update the cart button text for bundles If a product is a bundle, update the text of the product button. #### File: [ProductForm.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/app/components/ProductForm.tsx) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/bundles/patches/ProductForm.tsx.649d3b.patch)) ```diff @@ -11,9 +11,11 @@ import type {ProductFragment} from 'storefrontapi.generated'; export function ProductForm({ productOptions, selectedVariant, + isBundle, }: { productOptions: MappedProductOptions[]; selectedVariant: ProductFragment['selectedOrFirstAvailableVariant']; + isBundle: boolean; }) { const navigate = useNavigate(); const {open} = useAside(); @@ -118,7 +120,11 @@ export function ProductForm({ : [] } > - {selectedVariant?.availableForSale ? 'Add to cart' : 'Sold out'} + {selectedVariant?.availableForSale + ? isBundle + ? 'Add bundle to cart' + : 'Add to cart' + : 'Sold out'}
    ); ``` *** ## Step 10: Show bundle badges on product images If a product is a bundle, show the `BundleBadge` component in the `ProductImage` component. #### File: [ProductImage.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/app/components/ProductImage.tsx) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/bundles/patches/ProductImage.tsx.42c3b6.patch)) ```diff @@ -1,10 +1,13 @@ import type {ProductVariantFragment} from 'storefrontapi.generated'; import {Image} from '@shopify/hydrogen'; +import {BundleBadge} from './BundleBadge'; export function ProductImage({ image, + isBundle = false, }: { image: ProductVariantFragment['image']; + isBundle: boolean; }) { if (!image) { return
    ; @@ -18,6 +21,7 @@ export function ProductImage({ key={image.id} sizes="(min-width: 45em) 50vw, 100vw" /> + {isBundle ? : null}
    ); } ``` *** ## Step 11: Show bundle badges on product cards If a product is a bundle, show the `BundleBadge` component in the `ProductItem` component. #### File: [ProductItem.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/app/components/ProductItem.tsx) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/bundles/patches/ProductItem.tsx.d3223b.patch)) ## File ```diff @@ -1,24 +1,19 @@ import {Link} from 'react-router'; import {Image, Money} from '@shopify/hydrogen'; -import type { - ProductItemFragment, - CollectionItemFragment, - RecommendedProductFragment, -} from 'storefrontapi.generated'; +import type {ProductItemFragment} from 'storefrontapi.generated'; import {useVariantUrl} from '~/lib/variants'; +import {BundleBadge} from '~/components/BundleBadge'; export function ProductItem({ product, loading, }: { - product: - | CollectionItemFragment - | ProductItemFragment - | RecommendedProductFragment; + product: ProductItemFragment; loading?: 'eager' | 'lazy'; }) { const variantUrl = useVariantUrl(product.handle); - const image = product.featuredImage; + const isBundle = product?.isBundle?.requiresComponents; + return ( - {image && ( - {image.altText - )} -

    {product.title}

    - - - +
    + {product.featuredImage && ( + {product.featuredImage.altText + )} +

    {product.title}

    + + + + {isBundle && } +
    ); } ``` *** ## Step 12: Position bundle badges on images Make sure the bundle badge is positioned relative to the product image. #### File: [app.css](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/app/styles/app.css) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/bundles/patches/app.css.881a99.patch)) ```diff @@ -435,6 +435,10 @@ button.reset:hover:not(:has(> *)) { margin-top: 0; } +.product-image { + position: relative; +} + .product-image img { height: auto; width: 100%; ``` *** ## Next steps * Test your implementation by going to your store and adding a bundle to the cart. Make sure that the bundle's badge appears on the product page and in the cart. * (Optional) [Place a test order](https://help.shopify.com/en/manual/checkout-settings/test-orders) to see how orders for bundles appear in your Shopify admin. *** * [Requirements](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#requirements) * [Ingredients](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#ingredients) * [Step 1: Set up the Shopify Bundles app](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#step-1-set-up-the-shopify-bundles-app) * [Step 2: Create the Bundle​Badge component](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#step-2-create-the-bundlebadge-component) * [Step 3: Create a new Bundled​Variants component](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#step-3-create-a-new-bundledvariants-component) * [Step 4: Query bundle pricing for recommended products](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#step-4-query-bundle-pricing-for-recommended-products) * [Step 5: Show bundled products on the product page](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#step-5-show-bundled-products-on-the-product-page) * [Step 6: Detect bundles in collection listings](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#step-6-detect-bundles-in-collection-listings) * [Step 7: Identify bundles in the cart](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#step-7-identify-bundles-in-the-cart) * [Step 8: Show bundle badges in the cart](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#step-8-show-bundle-badges-in-the-cart) * [Step 9: Update the cart button text for bundles](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#step-9-update-the-cart-button-text-for-bundles) * [Step 10: Show bundle badges on product images](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#step-10-show-bundle-badges-on-product-images) * [Step 11: Show bundle badges on product cards](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#step-11-show-bundle-badges-on-product-cards) * [Step 12: Position bundle badges on images](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#step-12-position-bundle-badges-on-images) * [Next steps](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/bundles.md#next-steps)