Tag: List

How to Add Commas Between a List of Items Dynamically with CSS

Imagine you have a list of items. Say, fruit: Banana, Apple, Orange, Pear, Nectarine

We could put those commas (,) in the HTML, but let’s look at how we could do that in CSS instead, giving us an extra level of control. We’ll make sure that last item doesn’t have a comma while we’re at it.

I needed this for a real project recently, and part of the requirements were that any of the items in the list could be hidden/revealed via JavaScript. The commas needed to work correctly no matter which items were currently shown.

One solution I found rather elegant solution is using general sibling combinator. We’ll get to that in a minute. Let’s start with some example HTML. Say you start out with a list of fruits:

<ul class="fruits">   <li class="fruit on">Banana</li>   <li class="fruit on">Apple</li>   <li class="fruit on">Orange</li>   <li class="fruit on">Pear</li>   <li class="fruit on">Nectarine</li> </ul>

And some basic CSS to make them appear in a list:

.fruits {   display: flex;   padding-inline-start: 0;   list-style: none; }  .fruit {   display: none; /* hidden by default */ }  .fruit.on { /* JavaScript-added class to reveal list items */   display: inline-block; }

Now say things happen inside this interface, like a user toggles controls that filter out all fruits that grow in cold climates. Now a different set of fruits is shown, so the fruit.on class is manipulated with the classList API.

So far, our HTML and CSS would create a list like this:

BananaOrangeNectarine

Now we can reach for that general sibling combinator to apply a comma-and-space between any two on elements:

.fruit.on ~ .fruit.on::before {   content: ', ';  }

Nice!

You might be thinking: why not just apply commas to all the list items and remove it from the last with something like :last-child or :last-of-type. The trouble with that is the last child might be “off” at any given time. So what we really want is the last item that is “on,” which isn’t easily possible in CSS, since there is nothing like “last of class” available. Hence, the general sibling combinator trick!

In the UI, I used max-width instead of display and toggled that between 0 and a reasonable maximum value so that I could use transitions to push items on and off more naturally, making it easier for the user to see which items are being added or removed from the list. You can add the same effect to the pseudo-element as well to make it super smooth.

Here’s a demo with a couple of examples that are both slight variations. The fruits example uses a hidden class instead of on, and the veggies example has the animations. SCSS is also used here for the nesting:

I hope this helps others looking for something similar!


The post How to Add Commas Between a List of Items Dynamically with CSS appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,

How to Create a Timeline Task List Component Using SVG

I’m thoroughly convinced that SVG unlocks a whole entire world of building interfaces on the web. It might seem daunting to learn SVG at first, but you have a spec that was designed to create shapes and yet, still has elements, like text, links, and aria labels available to you. You can accomplish some of the same effects in CSS, but it’s a little more particular to get positioning just right, especially across viewports and for responsive development.

What’s special about SVG is that all the positioning is based on a coordinate system, a little like the game Battleship. That means deciding where everything goes and how it’s drawn, as well as how it’s relative to each other, can be really straightforward to reason about. CSS positioning is for layout, which is great because you have things that correspond to one another in terms of the flow of the document. This otherwise positive trait is harder to work with if you’re making a component that’s very particular, with overlapping and precisely placed elements.

Truly, once you learn SVG, you can draw anything, and have it scale on any device. Even this very site uses SVG for custom UI elements, such as my avatar, above (meta!).

That little half circle below the author image is just SVG markup.

We won’t cover everything about SVGs in this post (you can learn some of those fundamentals here, here, here and here), but in order to illustrate the possibilities that SVG opens up for UI component development, let’s talk through one particular use case and break down how we would think about building something custom.

The timeline task list component

Recently, I was working on a project with my team at Netlify. We wanted to show the viewer which video in a series of videos in a course they were currently watching. In other words, we wanted to make some sort of thing that’s like a todo list, but shows overall progress as items are completed. (We made a free space-themed learning platform and it’s hella cool. Yes, I said hella.)

Here’s how that looks:

So how would we go about this? I’ll show an example in both Vue and React so that you can see how it might work in both frameworks.

The Vue version

We decided to make the platform in Next.js for dogfooding purposes (i.e. trying out our own Next on Netlify build plugin), but I’m more fluent in Vue so I wrote the initial prototype in Vue and ported it over to React.

Here is the full CodePen demo:

Let’s walk through this code a bit. First off, this is a single file component (SFC), so the template HTML, reactive script, and scoped styles are all encapsulated in this one file.

We’ll store some dummy tasks in data, including whether each task is completed or not. We’ll also make a method we can call on a click directive so that we can toggle whether the state is done or not.

<script> export default {   data() {     return {       tasks: [         {           name: 'thing',           done: false         },         // ...       ]     };   },   methods: {     selectThis(index) {       this.tasks[index].done = !this.tasks[index].done     }   } }; </script> 

Now, what we want to do is create an SVG that has a flexible viewBox depending on the amount of elements. We also want to tell screen readers that this a presentational element and that we will provide a title with a unique id of timeline. (Get more information on creating accessible SVGs.)

<template>   <div id="app">     <div>       <svg :viewBox="`0 0 30 $ {tasks.length * 50}`"            xmlns="http://www.w3.org/2000/svg"             width="30"             stroke="currentColor"             fill="white"            aria-labelledby="timeline"            role="presentation">            <title id="timeline">timeline element</title>         <!-- ... -->       </svg>     </div>   </div> </template>

The stroke is set to currentColor to allow for some flexibility — if we want to reuse the component in multiple places, it will inherit whatever color is used on the encapsulating div.

Next, inside the SVG, we want to create a vertical line that’s the length of the task list. Lines are fairly straightforward. We have x1 and x2 values (where the line is plotted on the x-axis), and similarly, y1 and y2.

<line x1="10" x2="10" :y1="num2" :y2="tasks.length * num1 - num2" />

The x-axis stays consistently at 10 because we’re drawing a line downward rather than left-to-right. We’ll store two numbers in data: the amount we want our spacing to be, which will be num1, and the amount we want our margin to be, which will be num2.

data() {   return {     num1: 32,     num2: 15,     // ...   } }

The y-axis starts with num2, which is subtracted from the end, as well as the margin. The tasks.length is multiplied by the spacing, which is num1.

Now, we’ll need the circles that lie on the line. Each circle is an indicator for whether a task has been completed or not. We’ll need one circle for each task, so we’ll use v-for with a unique key, which is the index (and is safe to use here as they will never reorder). We’ll connect the click directive with our method and pass in the index as a param as well.

CIrcles in SVG are made up of three attributes. The middle of the circle is plotted at cx and cy, and then we draw a radius with r. Like the line, cx starts at 10. The radius is 4 because that’s what’s readable at this scale. cy will be spaced like the line: index times the spacing (num1), plus the margin (num2).

Finally, we’ll put use a ternary to set the fill. If the task is done, it will be filled with currentColor. If not, it will be filled with white (or whatever the background is). This could be filled with a prop that gets passed in the background, for instance, where you have light and dark circles.

<circle    @click="selectThis(i)"    v-for="(task, i) in tasks"   :key="task.name"   cx="10"   r="4"   :cy="i * num1 + num2"   :fill="task.done ? 'currentColor' : 'white'"   class="select"/>

Finally, we are using CSS grid to align a div with the names of tasks. This is laid out much in the same way, where we’re looping through the tasks, and are also tied to that same click event to toggle the done state.

<template>   <div>     <div        @click="selectThis(i)"       v-for="(task, i) in tasks"       :key="task.name"       class="select">       {{ task.name }}     </div>   </div> </template>

The React version

Here is where we ended up with the React version. We’re working towards open sourcing this so that you can see the full code and its history. Here are a few modifications:

  • We’re using CSS modules rather than the SCFs in Vue
  • We’re importing the Next.js link, so that rather than toggling a “done” state, we’re taking a user to a dynamic page in Next.js
  • The tasks we’re using are actually stages of the course —or “Mission” as we call them — which are passed in here rather than held by the component.

Most of the other functionality is the same 🙂

import styles from './MissionTracker.module.css'; import React, { useState } from 'react'; import Link from 'next/link';  function MissionTracker({ currentMission, currentStage, stages }) {  const [tasks, setTasks] = useState([...stages]);  const num1 = [32];  const num2 = [15];   const updateDoneTasks = (index) => () => {    let tasksCopy = [...tasks];    tasksCopy[index].done = !tasksCopy[index].done;    setTasks(tasksCopy);  };   const taskTextStyles = (task) => {    const baseStyles = `$ {styles['tracker-select']} $ {styles['task-label']}`;     if (currentStage === task.slug.current) {      return baseStyles + ` $ {styles['is-current-task']}`;    } else {      return baseStyles;    }  };   return (    <div className={styles.container}>      <section>        {tasks.map((task, index) => (          <div            key={`mt-$ {task.slug}-$ {index}`}            className={taskTextStyles(task)}          >            <Link href={`/learn/$ {currentMission}/$ {task.slug.current}`}>              {task.title}            </Link>          </div>        ))}      </section>       <section>        <svg          viewBox={`0 0 30 $ {tasks.length * 50}`}          className={styles['tracker-svg']}          xmlns="http://www.w3.org/2000/svg"          width="30"          stroke="currentColor"          fill="white"          aria-labelledby="timeline"          role="presentation"        >          <title id="timeline">timeline element</title>           <line x1="10" x2="10" y1={num2} y2={tasks.length * num1 - num2} />          {tasks.map((task, index) => (            <circle              key={`mt-circle-$ {task.name}-$ {index}`}              onClick={updateDoneTasks(index)}              cx="10"              r="4"              cy={index * +num1 + +num2}              fill={                task.slug.current === currentStage ? 'currentColor' : 'black'              }              className={styles['tracker-select']}            />          ))}        </svg>      </section>    </div>  ); }  export default MissionTracker;

Final version

You can see the final working version here:

This component is flexible enough to accommodate lists small and large, multiple browsers, and responsive sizing. It also allows the user to have better understanding of where they are in their progress in the course.

But this is just one component. You can make any number of UI elements: knobs, controls, progress indicators, loaders… the sky’s the limit. You can style them with CSS, or inline styles, you can have them update based on props, on context, on reactive data, the sky’s the limit! I hope this opens some doors on how you yourself can develop more engaging UI elements for the web.


The post How to Create a Timeline Task List Component Using SVG appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,
[Top]

How to Make a List Component with Emotion

I’ve been doing a bit of refactoring this week at Sentry and I noticed that we didn’t have a generic List component that we could use across projects and features. So, I started one, but here’s the rub: we style things at Sentry using Emotion, which I have only passing experience with and is described in the docs as…

[…] a library designed for writing css styles with JavaScript. It provides powerful and predictable style composition in addition to a great developer experience with features such as source maps, labels, and testing utilities. Both string and object styles are supported.

If you’ve never heard of Emotion, the general idea is this: when we’re working on big codebases with lots of components, we want to ensure that we can control the cascade of our CSS. So, let’s say you have an .active class in one file and you want to make sure that doesn’t impact the styles of a completely separate component in another file that also has a class of.active.

Emotion tackles this problem by adding custom strings to your classnames so they don’t conflict with other components. Here’s an example of the HTML it might output:

<div class="css-1tfy8g7-List e13k4qzl9"></div>

Pretty neat, huh? There’s lots of other tools and workflows out there though that do something very similar, such as CSS Modules.

To get started making the component, we first need to install Emotion into our project. I’m not going to walkthrough that stuff because it’s going to be different depending on your environment and setup. But once that’s complete we can go ahead and create a new component like this:

import React from 'react'; import styled from '@emotion/styled';  export const List = styled('ul')`   list-style: none;   padding: 0; `;

This looks pretty weird to me because, not only are we writing styles for the <ul> element, but we’re defining that the component should render a <ul>, too. Combining both the markup and the styles in one place feels odd but I do like how simple it is. It just sort of messes with my mental model and the separation of concerns between HTML, CSS, and JavaScript.

In another component, we can import this <List> and use it like this:

import List from 'components/list';  <List>This is a list item.</List>

The styles we added to our list component will then be turned into a classname, like .oefioaueg, and then added to the <ul> element we defined in the component.

But we’re not done yet! With the list design, I needed to be able to render a <ul> and an <ol> with the same component. I also needed a version that allows me to place an icon within each list item. Just like this:

The cool (and also kind of weird) thing about Emotion is that we can use the as attribute to select which HTML element we’d like to render when we import our component. We can use this attribute to create our <ol> variant without having to make a custom type property or something. And that happens to look just like this:

<List>This will render a ul.</List> <List as="ol">This will render an ol.</List>

That’s not just weird to me, right? It’s super neat, however, because it means that we don’t have to do any bizarro logic in the component itself just to change the markup.

It was at this point that I started to jot down what the perfect API for this component might look like though because then we can work our way back from there. This is what I imagined:

<List>   <ListItem>Item 1</ListItem>   <ListItem>Item 2</ListItem>   <ListItem>Item 3</ListItem> </List>  <List>   <ListItem icon={<IconBusiness color="orange400" size="sm" />}>Item 1</ListItem>   <ListItem icon={<IconBusiness color="orange400" size="sm" />}>Item 2</ListItem>   <ListItem icon={<IconBusiness color="orange400" size="sm" />}>Item 3</ListItem> </List>  <List as="ol">   <ListItem>Item 1</ListItem>   <ListItem>Item 2</ListItem>   <ListItem>Item 3</ListItem> </List>

So after making this sketch I knew we’d need two components, along with the ability to nest icon subcomponents within the <ListItem>. We can start like this:

import React from 'react'; import styled from '@emotion/styled';  export const List = styled('ul')`   list-style: none;   padding: 0;   margin-bottom: 20px;    ol& {     counter-reset: numberedList;   } `;

That peculiar ol& syntax is how we tell emotion that these styles only apply to an element when it’s rendered as an <ol>. It’s often a good idea to just add a background: red; to this element to make sure your component is rendering things correctly.

Next up is our subcomponent, the <ListItem>. It’s important to note that at Sentry we also use TypeScript, so before we define our <ListItem> component, we’ll need to set our props up first:

type ListItemProps = {   icon?: React.ReactNode;   children?: string | React.ReactNode;   className?: string; };

Now we can add our <IconWrapper> component that will size an <Icon> component within the ListItem. If you remember from the example above, I wanted it to look something like this:

<List>   <ListItem icon={<IconBusiness color="orange400" size="sm" />}>Item 1</ListItem>   <ListItem icon={<IconBusiness color="orange400" size="sm" />}>Item 2</ListItem>   <ListItem icon={<IconBusiness color="orange400" size="sm" />}>Item 3</ListItem> </List>

That IconBusiness component is a preexisting component and we want to wrap it in a span so that we can style it. Thankfully, we’ll need just a tiny bit of CSS to align the icon properly with the text and the <IconWrapper> can handle all of that for us:

type ListItemProps = {   icon?: React.ReactNode;   children?: string | React.ReactNode;   className?: string; };  const IconWrapper = styled('span')`   display: flex;   margin-right: 15px;   height: 16px;   align-items: center; `;

Once we’ve done this we can finally add our <ListItem> component beneath these two, although it is considerably more complex. We’ll need to add the props, then we can render the <IconWrapper> above when the icon prop exists, and render the icon component that’s passed into it as well. I’ve also added all the styles below so you can see how I’m styling each of these variants:

export const ListItem = styled(({icon, className, children}: ListItemProps) => (   <li className={className}>     {icon && (       <IconWrapper>         {icon}       </IconWrapper>     )}     {children}   </li> ))<ListItemProps>`   display: flex;   align-items: center;   position: relative;   padding-left: 34px;   margin-bottom: 20px; 	   /* Tiny circle and icon positioning */   &:before, 	& > $ {IconWrapper} {     position: absolute;     left: 0;   }    ul & {     color: #aaa;     /* This pseudo is the tiny circle for ul items */      &:before {       content: '';       width: 6px;       height: 6px;       border-radius: 50%;       margin-right: 15px;       border: 1px solid #aaa;       background-color: transparent;       left: 5px;       top: 10px;     } 		     /* Icon styles */     $ {p =>       p.icon &&       `       span {         top: 4px;       }       /* Removes tiny circle pseudo if icon is present */       &:before {         content: none;       }     `}   }   /* When the list is rendered as an <ol> */   ol & {     &:before {       counter-increment: numberedList;       content: counter(numberedList);       top: 3px;       display: flex;       align-items: center;       justify-content: center;       text-align: center;       width: 18px;       height: 18px;       font-size: 10px;       font-weight: 600;       border: 1px solid #aaa;       border-radius: 50%;       background-color: transparent;       margin-right: 20px;     }   } `;

And there you have it! A relatively simple <List> component built with Emotion. Although, after going through this exercise I’m still not sure that I like the syntax. I reckon it sort of makes the simple stuff really simple but the medium-sized components much more complicated than they should be. Plus, it could be pretty darn confusing to a newcomer and that worries me a bit.

But everything is a learning experience, I guess. Either way, I’m glad I had the opportunity to work on this tiny component because it taught me a few good things about TypeScript, React, and trying to make our styles somewhat readable.

The post How to Make a List Component with Emotion appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

List Style Recipes

Lists are a fundamental part of HTML! They are useful in things like blog posts for listing out steps, recipes for listing ingredients, or items in a navigation menu. Not only are they an opportunity for styling, but they have accessibility implications. For example, the number of items in a list is announced in a screen reader to give some context to the list.

Let’s focus on styling lists here, mostly just ordered and unordered lists (with apologies for snubbing our friend the definition list), and somewhat unusual styling situations.

The Basics

Before you do anything too fancy, know that there is quite few settings for list-style-type that might cover your needs out of the gate.

The Break in the Middle

Ordered lists can start at any number you want them to.

The Nested Decimals

The Reversed Top 10 List

A single reversed attribute will do the trick.

Image Bullets

The best bet is using a background-image on a pseudo-element. You’d think list-style-image would be the way to go, but it’s extremely limited. For example, you can’t position it or even resize it.

Emoji Bullets

Hand-Picked “Random” Order

The value attribute will set a list item to use the marker relevant for that position.

Custom Text Counters

Can be done with pseudo-elements for the most control, but there is also list-style-type: '-';

Inside vs. Outside

Things line up nicer with list-style-position: outside; (the default value), but the list markers render outside the box, so you have to be careful not to cut them off. They could hang off the edge of the browser window, or overflow: hidden; will hide them completely. The last two examples here have a trick to mimic the nicer alignment while rendering inside the element.

Colored Bullets

Three ways here:

  1. ::marker (newest and easiest)
  2. Classic pseudo-element style
  3. background-image (this one is an SVG Data URL so you can manipulate the color from the CSS)

Columized List

The number of columns can be automatic.

Colored Circle Numbers

Custom Cycling List Symbols

One-offs can be done with list-style: symbols() and reusable sets can be made with @counter-style then used. Note this is only supported in Firefox at the time of writing.

The post List Style Recipes appeared first on CSS-Tricks.

CSS-Tricks

, ,
[Top]

Did You Know the Ordered List Element Has Start and Reversed Attributes?

I sure didn’t! Tomek Sułkowsi shows how we can reverse the numbering of ordered lists with a simple HTML attribute:

<ol reversed>   <li>Apple</li>   <li>Banana</li>   <li>Pear</li> </ol>

And the start attribute can be added to begin the list at a number other than one, like this:

<ol start="2">   <li>Apple</li>   <li>Banana</li>   <li>Pear</li> </ol>

I’m not sure how I never knew about these properties! I guess I can see how they might come in handy in the future. There are plenty of times when we need to break up ordered lists here on CSS-Tricks with things like code blocks and having a way to pick a list back up where it left off is a nice convenience.

The post Did You Know the Ordered List Element Has Start and Reversed Attributes? appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]

Finally, it Will Be Easy to Change the Color of List Bullets

In my germinating years, the general advice was this:

<ul>   <li><span>List item</span></li>   <!-- ... --> </ul>
li { color: red; } /* bullet */ li span (color: black; } /* text */

Not terrible, but not great. You’re “resetting” everything at the span level, so it gets more complicated the more you do.

Things are getting much easier. Let’s take a walk through this world getting more modern as we go.


An alternative was to rip off the default list styling and replace it with a pseudo-element.

ul {   list-style: none; }  li::before {   content: "• ";   color: red; }

If we need to count, we could do that with CSS counters.

ol {   list-style: none;   counter-reset: my-awesome-counter; }  ol li {   counter-increment: my-awesome-counter; }  ol li::before {   content: counter(my-awesome-counter) ". ";   color: red; }

Quick aside here: this doesn’t help with the color, but you can specify what character to use for the bullet by setting a string, like:

ul {   list-style-type: '✽ '; }

This is as of Firefox 39 (2015) and Chrome 79 (which comes out Dec 9, 2019).

For ordered lists, there is a ton of language-specific options. And those language styles work for CSS counters as well, which you can learn more about in Hui Jing’s deep dive.

See the Pen
Random CSS counters playground
by Chen Hui Jing (@huijing)
on CodePen.


But all the while, we only wanted to select the stupid bullet (or whatever it is) and style it. Now we are starting to be able to do just that.

As of Firefox 68 (July 2019), you can do like:

li::marker {   color: red;   content: "►"; }

…which, as you can see, changes the color and the bullet thing That is definitely the cleanest and easiest way to go, so it’s nice to see progress.

Tejas demonstrates:

See the Pen
::marker example
by Tejas (@tejask)
on CodePen.

Manuel Matuzović notes that if you set an element to a list-item display type, you can use markers on them as well.

h2 {   display: list-item; }  h2::marker {   color: orange;   content: "☞"; }

Even Safari has support at the time of this writing, so we should lean on Chrome here.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

Chrome Opera Firefox IE Edge Safari
No No 68 No No 11.1

Mobile / Tablet

iOS Safari Opera Mobile Opera Mini Android Android Chrome Android Firefox
11.3-11.4 No No No No No

The post Finally, it Will Be Easy to Change the Color of List Bullets appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]