Build a product gallery app easily with TailwindCSS and StorefrontUI
Tags: tailwindcss, vue, javascript, typescript
Published: 2023-04-28 14:37:11

---
title: "Build a product gallery app easily with TailwindCSS and StorefrontUI"
description: "How to develop a product gallery Nuxt application with StorefrontUI and TailwindCSS."
published: true
img: "articles/Nuxt/tailwind_nuxt"
tags: ['tailwindcss', 'vue', 'javascript', 'typescript']
cover_image: https://res.cloudinary.com/mayashavin/image/upload/v1682692285/articles/Nuxt/storefront_nuxt_gallery.png
series: Efficient E-commerce with Storefront UI
published: true
canonical_url: https://mayashavin.com/articles/product-gallery-nuxt-storefront-ui
---

_Working with a component library to build your custom UI can be complicated, especially when you want to integrate with other tools, such as TailwindCSS. In this post, we will explore how to build a product gallery with Storefront UI, a fast and beautiful component library for E-commerce, and TailwindCSS in a Nuxt application._  

## Table of Content

- [Table of Content](#table-of-content)
- [Prerequisites](#prerequisites)
- [Storefront UI 2 as the robust E-commerce Component Library](#storefront-ui-2-as-the-robust-e-commerce-component-library)
  - [Installing Storefront UI with TailwindCSS](#installing-storefront-ui-with-tailwindcss)
- [Building a product gallery with grid view mode](#building-a-product-gallery-with-grid-view-mode)
  - [Implementing ProductCard component](#implementing-productcard-component)
- [Using SfLink with Nuxt](#using-sflink-with-nuxt)
  - [Resources](#resources)
  - [Summary](#summary)

## Prerequisites

It would be best if you had your Nuxt application set up and ready to go by using the following command:

```bash
npx nuxi init storefrontui-demo
```

Which `storefrontui-demo` is the name of our code demo application. You can change to any name per your preference.

Since Storefront UI is designed with TailwindCSS in mind, you need to install the TailwindCSS module for Nuxt with the following command:

```bash
yarn add -D @nuxtjs/tailwindcss @storefront-ui/vue
```

In `nuxt.config.ts`, we add the TailwindCSS module as follows:

```typescript
export default defineNuxtConfig({
  modules: ['@nuxtjs/tailwindcss']
})
```

To prepare for our gallery demo, we need to add some sample data for our gallery in `listing.json`, following the the `Product` declaration interface (`types/Product.type.ts`) since we are working with TypeScript:

```typescript
export interface Product {
    title: string;
    description: string;
    images: string[];
    quantity: number;
    price: number;
    ratings: number[];
    currency_code: string;
    sku: string;
}
```

And with that, we are ready for our tutorial!

## Storefront UI 2 as the robust E-commerce Component Library

Starting as a component library in Vue projects, Storefront UI aims to deliver to developers a set of fast, accessible, and customizable components for E-commerce exclusively. From Storefront UI 1.x to StorefrontUI 2.x, there have been significant improvements, both in the developer experience (DX) and in the user experience (UI) of the library. 

<img src="https://res.cloudinary.com/mayashavin/image/upload/f_auto,q_auto/v1675680195/articles/Nuxt/storefrontui" loading="lazy" alt="Storefront UI Website" class="mx-auto"/>

Compared to Storefront UI 1.x, Storefront UI 2.x leans towards to more composable (or building block) approach, where you can:
 
* Use [the base components](https://docs.storefrontui.io/v2/vue/components.html) to compose higher-level blocks, or 
* Use the [UI logic hooks (or composables)](https://docs.storefrontui.io/v2/vue/components.html) to create your custom UI components, or/and
* Use the [Block examples](https://docs.storefrontui.io/v2/vue/blocks.html) as the reference for your higher-level building blocks. 

At the time of writing, Storefront UI is at 2.1.0, freshly released from the oven. It also supports React, Vue, and soon Angular!

How about we start to use StorefrontUI in our application? Let's check it out, shall we?

### Installing Storefront UI with TailwindCSS

We first install the Storefront UI package for Vue using the following command:

```bash
yarn add -D @storefront-ui/vue
```

Then in `tailwind.config.ts`, we will import the Storefront UI presets for Tailwind to have the SFUI utility classes detected, as follows:

```typescript
// tailwind.config.ts
import type { Config } from 'tailwindcss';
import { tailwindConfig } from '@storefront-ui/vue/tailwind-config';

export default <Config>{
  presets: [tailwindConfig],
  content: ['./**/*.vue', './node_modules/@storefront-ui/vue/**/*.{js,mjs}'],
};
```

And we now can use components of StorefrontUI and start building our product gallery!

## Building a product gallery with grid view mode

For our product gallery - `components\ProductGallery.vue`, we want it to display a list of products in a grid (card) view with the following base template:

```html
<template>
    <h1 class="my-4 text-3xl font-extrabold text-gray-900 md:text-4xl text-center">All products</h1>
    <ul class="mx-8 my-4 flex flex-wrap gap-3 justify-center">
        <li v-for="product in products" :key="product.sku">
            <!--gridView-->
        </li>
    </ul>
</template>
```

And in the `<script setup>` section, we get the products from a composable `useProducts()`, as below:

```html
<script setup lang="ts">
import { useProducts } from '~/composables/useProducts';

const { products } = useProducts();
</script>
```

Now let's create a `composables/useProducts.ts` file which will return a reactive list of `products` as our sample data:

```typescript
import { Product } from '~/types/Product.type';
import data from '../listing.json';

export const useProducts = ():{ products: Ref<Product[]> } => {
    const products = reactive(data as any as Product[]);

    return {
        products
    }
}
```

Note if you are fetching data from an external API and updating the entire `products` array's value (or reference), you should use `ref()` instead of `reactive()`. 

So far, so good. Next, we implement the `ProductCard` component for our gallery's grid view.

### Implementing ProductCard component

Since ProductCard is a product's concise snapshot in a card view, we will need to display the following essential details:

* Product image
* Product's title
* Product ratings and how many votes it has
* Product's price, with currency code.

<img src="https://res.cloudinary.com/mayashavin/image/upload/f_auto,q_auto/v1675680195/articles/Nuxt/product_card_design" loading="lazy" alt="The Product Card component design" class="mx-auto"/>

Also, it needs to offer users some actions, including adding to the cart and making it a user's favorite. So, how should we implement it?

We can start from scratch with some help of the following base components from Storefront UI:

* `SfLink` for displaying the product's page URL.
* `SfButton` for the actions
* `SfIconShoppingCart` and `SfIconFavorite` for the action icons.
* `SfRating` to display the rating score graphically (using stars) and beautifully.
* `SfCounter` to display the number of the voters according to the format `(<voters>)` like `(11)`.

All these components come with the option to override their default template or a specific section of its template using a named slot pattern, giving you the full capability to build your ProductCard on top of them.

Or, with a less-code approach, we can head to [Product Card Vertical - Storefront UI](https://docs.storefrontui.io/v2/vue/blocks/ProductCard.html#productcard-vertical) page, where the team has prepared the ready-to-use example code snippet for us to reuse.

<img src="https://res.cloudinary.com/mayashavin/image/upload/f_auto,q_auto/v1675680195/articles/Nuxt/productcard_ui" loading="lazy" alt="The Product Card component preview" class="mx-auto"/>

Let's hit the Code tab and copy the code to your `components/ProductCard.vue`. We now have the necessary code for our component implemented. 

<img src="https://res.cloudinary.com/mayashavin/image/upload/f_auto,q_auto/v1675680195/articles/Nuxt/productcard_code" loading="lazy" alt="The Product Card component code preview" class="mx-auto"/>

The snippet comes with hard-coded data for product info, so we still need to modify it to match our needs.

In the `script` section, we will modify the code to make sure our component receives `item` as a prop type of `Product`, manually calculates the `totalRatings` from `item.ratings` array, and map `item.images[0]` to `productImage` as the image link, to the following:

```ts
import { SfRating, SfCounter, SfLink, SfButton, SfIconShoppingCart, SfIconFavorite } from '@storefront-ui/vue';
import type { Product } from '../types/Product.type';
import type { PropType } from 'vue';

const props = defineProps({
    item: {
        type: Object as PropType<Product>,
        required: true
    }
})

const totalRatings = computed(() => {
    const rating = prop.item.ratings.reduce((a, b) => a + b, 0) / prop.item.ratings.length;
    return +(rating.toFixed(1));
});

const productImage = computed(() => props.item.images[0])
```

And then, in the `template` section, we replace the **second** `div` element, which contains all the **static data** to the following:

```html
<div class="p-4 border-t border-neutral-200 flex-1" id="product-card--details">
    <SfLink :href="`/products/${item.sku}`" variant="secondary" class="no-underline"> {{ item.title }} </SfLink>
    <div class="flex items-center pt-1">
        <SfRating size="xs" :value="totalRatings" :max="5" />    
        <SfLink href="#" variant="secondary" class="pl-1 no-underline">
            <SfCounter size="xs">{{ item.ratings.length }}</SfCounter>
        </SfLink>
    </div>
    <span class="block pb-2 font-bold typography-text-lg">{{ item.currency_code }}{{ item.price }}</span>
    <SfButton type="button" size="sm" class="m-4 w-fit self-end">
        <template #prefix>
          <SfIconShoppingCart size="sm" />
        </template>
        Add to cart
    </SfButton>
</div>
```

In which, we map `item.title` and `/products/${item.sku}` as the label and the href of `SfLink` , `totalRatings` as the `value` of `SfRating`, `item.ratings.length` as the label for `SfCounter`, and display the item's price according to our wished format.

We also to change the `alt` for the product image to `item.title` instead of the default text `Great product`, for accessibility, as follows:

```html
<img
    :src="productImage"
    :alt="item.title"
    class="block object-cover h-auto rounded-md aspect-square"
    :height="300"
/>
```

Lastly, we place the wrapper `div` to `article` element for better HTML usage practice. Our final template structure will look like below:


```html
<article class="border border-neutral-200 rounded-md hover:shadow-lg max-w-[300px] h-full flex flex-col">
    <div class="relative">
        <!--image & favorite icons-->
    </div>
    <div id="product-card--details">
        <!---product information and add to card-->
    </div>
</article>
```

That's it. We only need to use it in our `ProductGallery.vue` component, and our gallery component is complete.

```html
<!--ProductGallery.vue-->
<template>
    <h1 class="my-4 text-3xl font-extrabold text-gray-900 md:text-4xl text-center">All products</h1>
    <ul class="mx-8 my-4 flex flex-wrap gap-3 justify-center">
        <li v-for="product in products" :key="product.sku">
            <ProductCard :item="product" />
        </li>
    </ul>
</template>
```

After we replace the `NuxtWelcome` will `ProductGallery` in `app.vue` and run the project using `yarn dev`, we will see our product gallery displayed in the browser as in the following screenshot:

<img src="https://res.cloudinary.com/mayashavin/image/upload/f_auto,q_auto/v1675680195/articles/Nuxt/product_gallery" loading="lazy" alt="The Product Card component preview" class="mx-auto"/>

## Using SfLink with Nuxt

One important note when using `SfLink` with Nuxt is that `SfLink` will render to an anchor element that is out of Nuxt context, causing a full page loading/reloading every time a user clicks on it. Also, it will not trigger any watcher on the router or route object using `watch`. Hence, to fix it, you need to render `SfLink` as a `NuxtLink` component, following the below steps:

* Resolve the `NuxtLink` as a Vue component using `resolveComponent`

```typescript
import { resolveComponent } from 'vue';
const NuxtLink = resolveComponent('NuxtLink');
```

* In `SfLink`, bind `NuxtLink` to `tag` prop:

```html
<SfLink :href="`/products/${item.sku}`" :tag="NuxtLink">
    <!--...-->    
</SfLink>
```

Now when clicking on the link, the app will work as expected. 

### Resources

You can find the working code [here](https://github.com/mayashavin/storefrontui-nuxt-demo)

### Summary

In this post, we have explored how to use Storefront UI documentation to create a product gallery using Storefront UI, TailwindCSS, and Nuxt at minimum effort. Indeed, there is still work to do, and we can continuously improve our app and component to work better, such as adding a list view for our gallery using [ProductCard Horizontal component](https://docs.storefrontui.io/v2/vue/blocks/ProductCard.html#productcard-horizontal) snippet.

Building blocks are not a new concept in the component library. Still, with Storefront UI, you can enjoy the benefits of customization and a ready-to-use essential component system as the base for your project. If you have built any components for e-Commerce using Storefront UI, share them with me in the comment section!

👉 _If you'd like to catch up with me sometimes, follow me on [Twitter](https://twitter.com/MayaShavin) | [Facebook](https://www.facebook.com/mayashavin/)._

👉 _Learn about Vue with my new book [Learning Vue](https://www.oreilly.com/library/view/learning-vue/9781492098812/). The early release is available now!_

Like this post or find it helpful? Share it 👇🏼 😉