DEV Community

Cover image for Build a product gallery app easily with TailwindCSS and StorefrontUI
Maya Shavin 🌷☕️🏡
Maya Shavin 🌷☕️🏡

Posted on • Originally published at mayashavin.com

Build a product gallery app easily with TailwindCSS and StorefrontUI

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

Prerequisites

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

npx nuxi init storefrontui-demo
Enter fullscreen mode Exit fullscreen mode

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:

yarn add -D @nuxtjs/tailwindcss @storefront-ui/vue
Enter fullscreen mode Exit fullscreen mode

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

export default defineNuxtConfig({
  modules: ['@nuxtjs/tailwindcss']
})
Enter fullscreen mode Exit fullscreen mode

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:

export interface Product {
    title: string;
    description: string;
    images: string[];
    quantity: number;
    price: number;
    ratings: number[];
    currency_code: string;
    sku: string;
}
Enter fullscreen mode Exit fullscreen mode

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.

Storefront UI Website

Compared to Storefront UI 1.x, Storefront UI 2.x leans towards to more composable (or building block) approach, where you can:

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:

yarn add -D @storefront-ui/vue
Enter fullscreen mode Exit fullscreen mode

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

// 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}'],
};
Enter fullscreen mode Exit fullscreen mode

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:

<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>
Enter fullscreen mode Exit fullscreen mode

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

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

const { products } = useProducts();
</script>
Enter fullscreen mode Exit fullscreen mode

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

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

The Product Card component design

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 page, where the team has prepared the ready-to-use example code snippet for us to reuse.

The Product Card component preview

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.

The Product Card component code preview

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:

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])
Enter fullscreen mode Exit fullscreen mode

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

<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>
Enter fullscreen mode Exit fullscreen mode

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:

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

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

<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>
Enter fullscreen mode Exit fullscreen mode

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

<!--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>
Enter fullscreen mode Exit fullscreen mode

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:

The Product Card component preview

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
import { resolveComponent } from 'vue';
const NuxtLink = resolveComponent('NuxtLink');
Enter fullscreen mode Exit fullscreen mode
  • In SfLink, bind NuxtLink to tag prop:
<SfLink :href="`/products/${item.sku}`" :tag="NuxtLink">
    <!--...-->    
</SfLink>
Enter fullscreen mode Exit fullscreen mode

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

Resources

You can find the working code here

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 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 | Facebook.

👉 Learn about Vue with my new book Learning Vue. The early release is available now!

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

Top comments (0)