DEV Community

Vue Mastery team for Vue Mastery

Posted on • Originally published at vuemastery.com on

Nuxt Icon

Nuxt Icon

Written by David Atanda

Nuxt Icon is a module that allows Nuxt developers to directly access hundreds of thousands of everyday icons within their app components. It presents a fast, elegant, and performant-friendly way to add icons to our app’s components. The module greatly simplifies the developer experience of working with icons by giving instant access to icons in a simple way rather than manually downloading SVG icons and adding them to your app.

Nuxt Icon pulls its icons from Iconify’s API which is an open-source repository for icons in general. This allows us to access a large number of icons that are constantly increasing based on the open-source contributions.

Installation

Run this command in the terminal to install Nuxt-Icon with npm :

npm install --save-dev nuxt-icon
Enter fullscreen mode Exit fullscreen mode

If you’re using yarn, make sure to run this instead:

yarn add --dev nuxt-icon
Enter fullscreen mode Exit fullscreen mode

Next, we’ll have to add nuxt-icon to our nuxt.config.ts file to initialize it within our app.

import { defineNuxtConfig } from 'nuxt'
export default defineNuxtConfig({
  modules: ['nuxt-icon']
})
Enter fullscreen mode Exit fullscreen mode

Usage

To use the icons in our app, we’d have to use the component. This component has three main props: name, color, and size.

  • The name prop represents the icon’s name. You can find the name of your intended icon inside iconify’s site.
  • We use color to define the literal color of the icon.
  • size describes the height and width of the icon

After installing Nuxt-Icon you can use it by just adding the component. You now add the props above to the component to display the correct icon.

For example, here we add the name and color props to the component.

<Icon name="mingcute:add-fill" color="black" />
Enter fullscreen mode Exit fullscreen mode

You can also convert an emoji into an SVG icon by using the emoji as the icon’s name

<Icon name="🚀" />
Enter fullscreen mode Exit fullscreen mode

Custom Icon

Finally, we can use a custom icon within our app. It just has to be within the components/global/ folder.

For example, we have a custom icon called CustomIcon.

<Icon name="CustomIcon" />
Enter fullscreen mode Exit fullscreen mode

In this case, CustomIcon is imported from components/global/CustomIcon.vue

// CustomIcon.vue
<template>
  <svg width="47.63" height="32" viewBox="0 0 256 172"><path fill="#80EEC0" d="M112.973 9.25c-7.172-12.333-25.104-12.333-32.277 0L2.524 143.66c-7.172 12.333 1.794 27.749 16.14 27.749h61.024c-6.13-5.357-8.4-14.625-3.76-22.576L135.13 47.348L112.973 9.25Z" /><path fill="#00DC82" d="M162.505 38.733c5.936-10.09 20.776-10.09 26.712 0l64.694 109.971c5.936 10.091-1.484 22.705-13.357 22.705H111.167c-11.872 0-19.292-12.614-13.356-22.705l64.694-109.971Z" /></svg>
</template>
Enter fullscreen mode Exit fullscreen mode

Config

We can create a configuration in our app for Nuxt-Icon. This config is a set of rules for the default state of our and its props. This can be helpful when building a large project where you need to abstract certain parts of the component to make sure there’s not a lot of repetition, allowing you to stick to the DRY (Don’t Repeat Yourself) principle.

Now, let’s define a single configuration that’s applicable everywhere in our app. To do that, create an app.config.ts file at the root of the project and set up nuxtIcon.

export default defineAppConfig({
  nuxtIcon: {
    size: '20px', 
    class: 'app-icon', 
    aliases: {
      'mLogo': 'logos:medium',
    }
  }
})
Enter fullscreen mode Exit fullscreen mode

In the config above, we’re defining the size of all components in our app as 20px and setting the CSS class to app-icon. The aliases object allows us to assign a name to the actual icon name as mentioned earlier; usually a simpler and intuitive name. In the configabove, we’re replacing logos:medium with mLogo.

Please note that without writing a custom config, the default size of all will remain 1emand the default class, icon.

An additional object you can add to the config file is iconifyApiOptions. It contains a property called url which is only useful if you have a self-hosted version of iconify; you can add the URL endpoint of your version. In this case, you can also add a publicApiFallback property that’s a boolean to determine whether to fall back to the public Iconify API if the self-hosted API doesn’t work. This fallback only works for the component, not for the component (we’ll discuss what the component is in a bit).

export default defineAppConfig({
  nuxtIcon: {
    // ...
    iconifyApiOptions: {
      url: 'https://<your-api-url>',
      publicApiFallback: true 
    }
  }
})
Enter fullscreen mode Exit fullscreen mode

You can find more details about the config and all its available properties here.

Render Function

You can display an in your Nuxt component using the render function. First, you import it like this:

import { Icon } from '#components'
Enter fullscreen mode Exit fullscreen mode

Then assign it to a variable called CustomIcon. Keep in mind that while assigning it to a variable, we have to define the props. The name prop is defined as logos:medium and color is defined as black.

const CustomIcon = h(Icon, { name: 'logos:medium', color: 'black' })
Enter fullscreen mode Exit fullscreen mode

Go ahead to use it as a component within our app’s component as .

<script setup>
import { Icon } from '#components'const CustomIcon = h(Icon, { name: 'logos:medium', color: 'black' })
</script>
<template>
  <p><CustomIcon /></p>
</template>
Enter fullscreen mode Exit fullscreen mode

Let’s go back to what is. It uses the icon as a mask-image and uses to eventually render the icon on your browser. It’s mostly for performance reasons than anything because besides the component name, every other thing is the same.

<template>
  <IconCSS name="logos:medium" />
</template>
Enter fullscreen mode Exit fullscreen mode

P.S: is currently experimental with Nuxt UI and is constantly changing.

Now that we’ve gone through Nuxt Icon and how to use it within our regular applications, let’s now replicate Medium’s onboarding screen using Nuxt Icon.

Building Our Onboarding Screen

Now that we’ve been introduced to Nuxt Icon and its properties, it’s time to put it into practice by building with it. Let’s take a look at what we’ll be building.

Our demo app is the onboarding screen for Medium users. While replicating this screen, we’d be implementing all icons using Nuxt-icon. Let’s jump right in:

Installation

To get started, we’ll need to set up our Nuxt project. Run this command in your terminal:

npx nuxi@latest init nuxt-medium
Enter fullscreen mode Exit fullscreen mode

Navigate into your project’s directory and initiate your project:

cd nuxt-medium

npm run dev
Enter fullscreen mode Exit fullscreen mode

Now we can install Nuxt-Icon. Run this command in your terminal:

npm install --save-dev nuxt-icon

#or

yarn add --dev nuxt-icon
Enter fullscreen mode Exit fullscreen mode

Register the nuxt-icon module by adding it to the modules array in your nuxt.config.ts file.

export default defineNuxtConfig({
  modules: ["nuxt-icon"],
  devtools: { enabled: true },
});
Enter fullscreen mode Exit fullscreen mode

Styling and Typography

Let’s start out by adding the right fonts to the project. Go into our sample repo and download these two fonts: GT-Super-Display-Light-Trial and Sohne. Once they’ve been downloaded, create a fonts folder inside our project’s public folder and have them live there. So both font files live inside the public/fonts folder.

We’re going to be styling our project using SASS. Feel free to use plain CSS if you want

Within the root folder, create an assets folder, then create a scss folder inside of it. Now, inside the assets/scss folder, we’re going to create four stylesheet files: base.scss ( foundational styles for our HTML elements), layout.scss(page styles), main.scss (where we import all the style files), and typography.scss(our font styles).

The goal here is to add our custom font files to the stylesheet. Within our typography.scss, we add these fonts using @font-face.

// typography.scss
@font-face {
  font-family: "GT-Super-Display-Light-Trial";
  src: url("/fonts/GT-Super-Display-Light-Trial.otf") format("opentype");
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "sohne";
  src: url("/fonts/sohne.otf") format('opentype'),;
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}
Enter fullscreen mode Exit fullscreen mode

For base.scss file, we take a bunch of the main HTML elements and give them a default style for our project. These include setting the margin, border, and padding to 0. Then, we go on to set the default font-family to "sohne" and vertical-align is set to baseline.

We then set our default display to block; while margin-block-start and margin-block-endare set to 0.

// base.scss
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
  margin: 0;
  padding: 0;
  border: 0;
  box-sizing: border-box;
  font-family: "sohne", Courier, monospace;
  vertical-align: baseline;
  margin-block-start: 0;
  margin-block-end: 0;
  display: block;
}
Enter fullscreen mode Exit fullscreen mode

For the html, set your font-size to 62.5% because we’re using rem to represent font-sizeinstead of px. So, the logic is that the root font size of our browsers is 16px, and we want to make it possible to convert our px to rem in tens. So, we calculate the percentage of 10px out of 16px as 62.5%.

Technically, this means that 10px = 1rem everywhere in our app. For instance a font-size of 14px can be defined as 1.4rem.

html {
  /* 62.5% of 16px browser font size is 10px */
  font-size: 62.5%;
}
.some-element {
  /* 1.4 * 10px = 14px */
  font-size: 1.4rem;
}
Enter fullscreen mode Exit fullscreen mode

Read more about this concept here.

Furthermore, there’s a media-query block for max-width: 600px, where we define our font-size as 55%.

html {
  font-size: 62.5%;
  @media screen and (max-width: 600px) {
    font-size: 55%;
  }
}
Enter fullscreen mode Exit fullscreen mode

For button, we’re defining the styles as none. The goal is set all default styling, from text-decoration, -webkit-appearance, and outline to none. We’re doing this so we can customize our buttons as we want.

button {
  text-decoration: none;
  -webkit-appearance: none;
  outline: none;
}
Enter fullscreen mode Exit fullscreen mode

Finally, we have ::-webkit-scrollbar where we hide our scroll-bar with display: none.

::-webkit-scrollbar {
  display: none;
}
// main.scss
@import "typography.scss";
@import "layout.scss";
@import "base.scss";
Enter fullscreen mode Exit fullscreen mode

Inside main.scss, we simply import our scss files. This allows us to export a single scss file into our nuxt.config.ts file.

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ["nuxt-icon"],
  css: ["~/assets/scss/main.scss"],
  devtools: { enabled: true },
});
Enter fullscreen mode Exit fullscreen mode

Within our nuxt.config.ts, we add a css property and link our main.scss file with ["~/assets/scss/main.scss"].

Layout

Next, we define our app’s layout. Go into your app.vue and create a parent div

with the CSS class .wrapper. This is going to act as a container for our app display.

// app.vue
<template>
  <div class="wrapper">

  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Now, let’s go into our layout.scss file in scss/layout.scss and style our .wrapper class. We’ll set the display to grid and set its content to the center with justify-content: center.

.wrapper {
  display: grid;
  justify-content: center;
}
Enter fullscreen mode Exit fullscreen mode

Header

Now, to the exciting part. Let’s create another div inside of the .wrapper div and add a class called .wrapper__inner.

<template>
  <div class="wrapper">
    <div class="wrapper__inner">

    </div>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Similarly, we’ll add the SCSS styles for this new div in layout.scss. Let’s define its display as grid and font-family as GT-Super-Display-Light-Trial.

.wrapper {
  ...
  &__inner {
    display: grid;
    font-family: "GT-Super-Display-Light-Trial", Courier, monospace;
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, we can add our component inside the div. Let’s call its class name wrapper__inner__icon, and its props include name, width, height, and color.

The name of the Icon is logos:medium, the width is 7em, height is 2.5em, and color is black.

<template>
  <div class="wrapper">
    <div class="wrapper__inner">
      <Icon
        class="wrapper __inner__ icon"
        name="logos:medium"
        width="7em"
        height="2.5em"
        color="black"
      />
    </div>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Directly beneath the , we can add the necessary text for the title of the onboarding page and its description.

<template>
  <div class="wrapper">
    <div class="wrapper__inner">
      <Icon
        class="wrapper __inner__ icon"
        name="logos:medium"
        width="7em"
        height="2.5em"
        color="black"
      />
      <h3 class="wrapper __inner__ title">What are you interested in?</h3>
      <p class="wrapper __inner__ select">Choose three or more.</p>
    </div>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Firstly, we’d give a general style to wrapper__inner__icon and wrapper__inner__select. We’ll set justify-self for each of them to center and font-size to 2rem.

.wrapper {
  display: grid;
  justify-content: center;
  &__inner {
    display: grid;
    font-family: "GT-Super-Display-Light-Trial", Courier, monospace;
    &__icon,
    &__select {
      justify-self: center;
      font-size: 2rem;
    }
    &__title {
      margin-top: 8rem;
      justify-self: center;
      margin-bottom: 4rem;
      font-size: 2.8rem;
      line-height: 3.2rem;
      font-weight: 400;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

For wrapper__inner__title, we’d set it’s margin-top to 8rem, margin-bottom to 4rem, and justify it to the center. For the font-size, it’ll be slightly more prominent than the icon at 2.8rem, line-height is set at 3.2rem, and font-weight at 400.

Let’s run our app’s development server

#yarn
yarn dev

#npm
npm run dev
Enter fullscreen mode Exit fullscreen mode

We should see our app displayed in our browser:

Options List (Categories)

Next, we’ll be displaying our list of categories for users to select while onboarding.

Let’s create a new div for this section. We’ll give the div a class of wrapper__options

<div class="wrapper__options">

</div>
Enter fullscreen mode Exit fullscreen mode

Now, let’s add styles for our .wrapper__options. Set the display to flex, width to 70%, and wrap the div’s content using flex-wrap: wrap.

Next, let’s add a margin of 4.8rem auto 13vh auto. This helps us to set div in the middle and add some margin on top and below.

To enable scrolling, we’ll add overflow: auto, define the gap between the div items as 1rem, and justify-content: center.

.wrapper {
  display: grid;
  justify-content: center;
  &__inner {
    ...
  }
  &__options {
    display: flex;
        width: 70%;
        flex-wrap: wrap;
    margin: 4.8rem auto 13vh auto;
    justify-content: center;
    gap: 1rem;
    overflow: auto;
    @media (max-height: 800px) {
      height: 60vh;
    }
  }
  &__footer {
    ...
  }
}
Enter fullscreen mode Exit fullscreen mode

For the content, let’s loop over each option. The list of options we want to display are topic categories for preferred articles on Medium. Within the root folder, create a new folder called data and add the file item-list.js. Copy and paste the file’s content from here.

// data/item-list.js
export const categories = [{ id: 1, icon: "ant-design:code-filled", name: "Programming", }, { ... }]
Enter fullscreen mode Exit fullscreen mode

Individual item in the categories array contains an id, the icon’s name, and the display name for each item.

Let’s import the categories array into app.vue and loop over its items. We’d be using v-forto loop over it with v-for="category in categories", and define the :key prop with category.id.

<script setup>
import { categories } from "/data/items-list.js";
</script>
<div class="wrapper__options">
  <div :key="category.id" v-for="category in categories">
    <OptionButton :category="category" />
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

We’re creating a new component to loop over called: . This component with be inside our components folder as components/OptionButton.vue.

Within the component, we define the props. In this case, we use defineProps to define the category prop; its type is an Object and it’s required.

Next, we’d want to change the UI of the button when a user clicks and selects a category. To do this, let’s create a reactive variable called toggleUser to track this behaviour.

// OptionButton.vue
<script setup>
import { ref } from "vue";
const props = defineProps({
  category: {
    type: Object,
    required: true,
  },
});
const { category } = props;
const toggleUser = ref(false);
</script>
Enter fullscreen mode Exit fullscreen mode

This entire component is a . Directly on the we have the @click event to change the style when it’s clicked. In our case, once it’s clicked we want to change the booleanvalue for toggleUser. We also add dynamic classes that takes effect based on boolean value.

Technically, anytime toggleUser is true, .wrapper__options__button__active style is added to the . All it does is change the border’s color to green when clicked.

The first in the button is the avi that displays the icon property that we’re passing into it through the category prop. We’ll set the :name prop to category.icon, size to 1.5em, and class is wrapper__options__button__avi. Then, we’ll display the category.name.

Next, we have two icons: the plus icon (clarity:plus-line)and check (iconamoon:check-light) icon. Let’s use v-if to display the plus icon when toggleUser is false and vice-versa with the check icon.

<template>
  <button
    @click="toggleUser = !toggleUser"
    :class="[
      toggleUser && 'wrapper __options__ button__active',
      'wrapper __options__ button',
    ]"
  >
    <Icon
      size="1.5em"
      class="wrapper __options__ button__avi"
      :name="category.icon"
    />
    {{ category.name }}
    <Icon
      v-if="!toggleUser"
      size="1.5em"
      name="clarity:plus-line"
    />
    <Icon
      v-else
      size="1.5em"
      name="iconamoon:check-light"
      color="green"
    />
  </button>
</template>
Enter fullscreen mode Exit fullscreen mode

For the styles, .wrapper__options__button has a transparent border and the border-radiusis 9.9rem. We also add other styles like setting the display to flex, aligning the items to the center, adding the appropriate font-size, and padding.

.wrapper {
  &__options {
    ... 
    &__button {
      border: 1px solid transparent;
      border-radius: 9.9rem;
      display: flex;
      align-items: center;
      font-size: 1.4rem;
      padding: 0.7rem 1.5rem;
      &__icon {
        margin-left: 0.8rem;
      }
      &__avi {
        margin-right: 0.8rem;
      }
      &__active {
        border: 1px solid green;
      }
    }
  }
  ...
}
Enter fullscreen mode Exit fullscreen mode

.wrapper__options__button__icon and .wrapper__options__button__avi both have a margin-left and margin-right of 0.8rem respectively. As mentioned earlier, .wrapper__options__button__active sets the border to the color green.

Footer

Go back into the app.vue, and inside of it, create a div at the bottom with the class .wrapper__footer with a with .wrapper__footer__button.

<template>
  <div class="wrapper">
    <div class="wrapper__inner">
      ...
    </div>
    <div class="wrapper__options">
      ...
    </div>
    <div class="wrapper__footer">
      <button class="wrapper __footer__ button">Continue</button>
    </div>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

For the styles, we set the height of .wrapper__footer as 13vh, width is 100%, bottom is 0, and position is fixed. We also set the appropriate line-height to 2, text-align is center, background-color is white, font-size is 3rem, and font-weight is bold.

.wrapper {
  ... 
  &__footer {
    line-height: 2;
    text-align: center;
    background-color: white;
    font-size: 3rem;
    font-weight: bold;
    position: fixed;
    bottom: 0;
    width: 100%;
    height: 13vh;
    &__button {
      font-family: "sohne", Courier, monospace;
      border: none;
      background-color: black;
      color: white;
      border-radius: 9.9rem;
      width: 45%;
      padding: 1.2rem 0;
      font-size: 1.4rem;
      margin: 1.2rem 0;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

.wrapper__footer__button has a font-family of "sohne", Courier, monospace. background-color is black, color is white, and border-radius is 9.9rem.

We set the 's width to 45%, padding is set to 1.2rem 0, we have font-size: 1.4rem;, border: none, and margin is 1.2rem 0.

Wrapping Up

Congratulations on making it this far! You have successfully learned about Nuxt-Icon and how to use it in your production app. Here is the source code and the live demo to this tutorial.

If you’d like to advance your learning, check out the official Nuxt-Icon documentation.

https://medium.com/media/dc9ed2ee6467c10b34d002942813c6d5/href

Originally published at https://www.vuemastery.com on December 14, 2023.


Top comments (0)