Pages

Thursday, May 19, 2022

Collapsible card - web component

Introduction

In previous post, we created simple collapsible card component with 0% javascript. In this post, I'm going to show an alternative way to build the same collapsible card using web components.

Web components allows us to create reusable custom elements — with their functionality encapsulated away from the rest of your code. This makes web components excellent tool build smart custom elements with built-in functionality.

Let's take the collapsible card from previous post. This "component" is missing a lot's of useful features. For example:
  • What if I want to use different icons on open/close? (Sure, we can implement different custom css classes, but this this is non-scalable solution)
  • What if I want to bind event handlers on various events: mouse click, card open, card close
  • What if I want to dynamically load card content?
As you can see, the list of suitable features can grow pretty fast and we cannot implement them in HTML / CSS only. 
 
The proper and easier way to solve these problems is to use existing frameworks / component libraries: Vue.js, Angular, React, swelte. But can we achieve the same goal in plain vanilla javascript, without using any framework? The answer is yes - we should use web components.

Every custom element is a class that derives from HTMLElement interface. We can be more specific in this case and build our custom collapsible card  component based on HTMLDetailsElement interface:


class MCard extends HTMLDetailsElement {
  constructor() {
    // Always call super first in constructor
    super();

    // Element functionality written in here

    ...
  }
}

we should always call super() first in constructor()

Next, we need to build HTML structure for our component from scratch:

See the Pen Untitled by mirushaki (@mirushaki) on CodePen.


As you can see, we are using <details> and <summary> tags internally. Also, we are transferring HTML content from <m-card> to <details> tag internally. The header/title is customizable by setting the "title" HTML attribute of
<m-card> tag. Finally, we are registering the component using customElements.define() function.

What about styles?

Sure, we can apply styles to our web-component. The proper way is to create <style> tag internally for each component. These styles will be fully encapsulated, because we are using shadow root. So, if we write: .p {font-size: 24px; } inside our component, it will not collide with other styles on page.

Let's expand our component by introducing some styles:

See the Pen Untitled by mirushaki (@mirushaki) on CodePen.


As you can see, now we have some customization: we can apply different icons for closed/opened state, we can apply colors for text/border and set boolean variable "open" if we want to specify "opened" state initially.

This is simple example how to use custom events with our web component. We are implementing custom "toggle" event, the web component itself handles internal "toggle" event coming from <details> tag, then creates a new toggle event with appropriate details: in this case passing "opened" value - true or false. Finally we have an event handler on custom <m-card> element that can receive "toggle" event and display appropriate alert message based on open/close state.


See the Pen Collapsible card - web component - events by mirushaki (@mirushaki) on CodePen.


Web components gives us much flexibility and customization. You can read more about web components here.

Wednesday, May 18, 2022

Collapsible Card with 0% javascript

Introduction

In this post, we will create collapsible card in HTML/CSS. The main challenge is to don't use any javascript code.

Example:

See the Pen Collapsible Card by mirushaki (@mirushaki) on CodePen.

HTML Structure

The component consist of 2 parts: the title and the body. Clicking on the title must toggle the visibility of the body. To achieve this behavior in pure HTML, we have to use specific HTML tags as building blocks of our component. These tags are: <details> and <summary>

The <details> tag specifies additional details that the user can open and close on demand. It acts as a wrapper.

The <summary> specifies a visible title for the details. It has to be the direct child of <details> tag. After the tag, there should be valid HTML content which will be visible when clicking on the title. So the structure of our component is:

<details>

  <summary>The Awesome Title</summary>

  <p>This is example body text</p>

</details>

NOTE: Instead of <p> tag, we can put any valid HTML content, including multiple tags after <summary> tag.

This is an example of the following code without any CSS:


See the Pen Collapsible Card - simple by mirushaki (@mirushaki) on CodePen.


Apply CSS Styles

Clicking on the title will toggle the visibility of body content. But there are some points that needs attention. The actual style will be user agent(browser) dependent. To maintain the same appearance between different browsers, we have to customize our component using CSS.

Let's introduce a class "m-card" and apply it to <details> tag. We will put every custom style inside.
Also to maintain proper margins/paddings we need to introduce a body wrapper <div> tag and apply m-card-content class to it. So, the HTML is:

<details class="m-card">

  <summary>The Awesome Title</summary>

  <div class="m-card-content">

          <p>This is example body text</p>

  </div>

</details>

Let's customize the title/header of our component, so we are styling <summary> tag of our component:

details.m-card > summary {

  font-size: 1.3rem;

  background-color: #ddd;

  padding: .5em;

  cursor: pointer;

}

The selector is details.m-card > summary, which means we are selecting every <summary> tag, which is the direct child of <details> tag where <details> tag has class named "m-card".
Let's make the title a little bit (1.3 times) bigger then default page text.

Note: I'm using "rem" units for font sizes. Here is a great video explaining different CSS units:
background-color and  padding styles are self-explanatory and finally we are setting cursor: pointer to display "pointer" cursor on hover. It makes component easier to use: the title is clickable and user is encouraged to click the content.


Now, let's write some styles for body content:

details.m-card > div.m-card-content {

  padding: .5em;

  border: 2px solid #ddd;

}

The selector is details.m-card > div.m-card-content, which means we are selecting every <div> tag, which has class named "m-card-content" and which is the direct child of <details> tag where <details> tag has class named "m-card". The actual sytles are self-explanatory.

But, what about the marker?

Good news, we can apply CSS styles to marker as well. By default, in Google Chrome marker is displayed as black triangle. We can use eye-catching icons instead!

The idea is the following: <summary> tag has pseudo element ::marker. We can apply limited number of properties to ::marker pseudo element. One of them is the "content" property.

CSS "content" property can accept UTF8 text as well as HTML emojis, Font awesome icons, Bootstrap icons, etc. So it's really handy and flexible.

See more on ::marker selector

Let's use ➕ and ➖ HTML emojis. Please, leave a comment if you are interested in how to use FontAwesome icons. When card is closed, we should display ➕ . When card is opened, we should display ➖.

details.m-card > summary::marker {
  content: "\2795";
}

details[open].m-card > summary::marker {
  content: "\2796";
}

When the card is opened, the <details> tag has "open" HTML attribute. That makes possible to distinguish between opened/closed states in CSS styles. You may wonder, what does 2795 and 2796 mean? These are hexadecimal codes for  HTML emojis. We need unicode escaping when using these codes as the values of "content" property, thereby we use "\" and then the hexadecimal code.

Here is the list of HTML emojis

See the Pen Collapsible Card by mirushaki (@mirushaki) on CodePen.

What about transitions and animations?

There are some workarounds to achieve transitions / animations using only CSS, but none of them works ideally. Thereby, I'll not post them in this blog.

This is just a simple component in only CSS / HTML for practicing. It is designed for very simple web-sites. It is always recommended to use well-known bootstrap components or web-component libraries to achieve maximum compatibility and avoid custom code.
However, if javascript is disabled, this workaround might become handy.