Create Interactive Add/Remove Cards with HTML, CSS, and JavaScript

Faraz

By Faraz -

Elevate your web development skills by learning how to build customizable card layouts that allow users to add or remove cards effortlessly.


Create Interactive Add Remove Cards with HTML, CSS, and JavaScript.webp

Table of Contents

  1. Project Introduction
  2. HTML Code
  3. CSS Code
  4. JavaScript Code
  5. Preview
  6. Conclusion

Adding and removing cards dynamically on a webpage can greatly enhance user interaction and engagement. In this comprehensive guide, we will walk through the process of creating such functionality using the powerful combination of HTML, CSS, and JavaScript. Whether you're a beginner or looking to expand your web development skills, this tutorial will provide you with step-by-step instructions and clear explanations to help you master the art of creating interactive card layouts. Let's dive in and explore how to bring your web pages to life with dynamic add/remove card features!

Source Code

Step 1 (HTML Code):

To begin, let's create the basic HTML structure for our cards. We'll use <li> elements to represent each card.

Let's break down the HTML code:

1. <!DOCTYPE html>: This declaration specifies the document type and version of HTML being used, which is HTML5 in this case.

2. <html lang="en">: This tag defines the root element of the HTML document and specifies the language of the content, which is English in this case.

3. <head>: This section contains metadata about the HTML document, such as character encoding, viewport settings, and the title of the webpage.

  • <meta charset="UTF-8">: This specifies the character encoding for the document, which is UTF-8 (Unicode Transformation Format 8-bit), allowing for a wide range of characters from various languages and scripts.
  • <meta http-equiv="X-UA-Compatible" content="IE=edge">: This meta tag is used to set the compatibility mode for Internet Explorer. Here, it's set to "IE=edge", which ensures that the latest version of Internet Explorer is used to render the webpage.
  • <meta name="viewport" content="width=device-width, initial-scale=1.0">: This meta tag sets the viewport properties for responsive web design. It ensures that the webpage adapts to different device screen sizes by setting the width to the device width and the initial scale to 1.0.
  • <title>Add/Remove Cards</title>: This sets the title of the webpage, which appears in the browser's title bar or tab.
  • <link rel="stylesheet" href="styles.css">: This links an external CSS stylesheet named "styles.css" to the HTML document, allowing for the styling of the webpage's elements.

4. <body>: This section contains the visible content of the webpage, including buttons, cards, a warning message, and a footer.

  • <button class="add-btn">: This is a button element with the class "add-btn", which presumably adds a new card when clicked.
  • <template id="card">: This is an HTML template element with the id "card", which serves as a template for creating new card elements dynamically through JavaScript.
  • <ul class="cards">: This is an unordered list element with the class "cards", containing several list items that represent individual cards.
  • <div class="warning">: This is a division element with the class "warning", which contains a warning message displayed to users whose browsers do not support a specific CSS feature.
  • <footer>: This section contains copyright information or additional links related to the webpage.

5. <script src="script.js"></script>: This script tag links an external JavaScript file named "script.js" to the HTML document, allowing for dynamic behavior and interactivity on the webpage.

Step 2 (CSS Code):

Next, we'll style our cards to give them a visually appealing layout. CSS will be used to define the appearance, including borders, colors, and spacing.

Let's break it down:

1. @layer view-transitions: This defines a layer for styles related to view transitions.

2. @layer no-root: Within this layer, styles for root elements are defined.

  • :root: Sets the view-transition-name property to "none", meaning no transition effect.
  • ::view-transition: Styles the pseudo-element for view transitions, setting pointer-events to "none".

3. @layer reorder-cards: This layer manages styles for reordering cards.

  • @supports (view-transition-class: card): Checks if the browser supports the specified view transition class.
  • .warning: Hides the warning message if the browser supports the view transition class.
  • :root: Defines a custom easing function using --bounce-easing.
  • .card: Assigns the view transition class "card" to card elements.
  • ::view-transition-group(*.card): Styles the view transition group for card elements, specifying animation timing and duration using the custom easing function.

4. @layer add-card: Manages styles for adding cards.

  • @keyframes animate-in: Defines a keyframe animation for adding cards, gradually changing opacity and position.
  • ::view-transition-new(targeted-card):only-child: Styles the transition for newly added cards, applying the defined animation.

5. @layer remove-card: Manages styles for removing cards.

  • @keyframes animate-out: Defines a keyframe animation for removing cards, gradually changing opacity and position.
  • ::view-transition-old(targeted-card):only-child: Styles the transition for removing cards, applying the defined animation.

6. @layer base: This layer contains base styles for the webpage.

  • body: Sets up grid display for the body, centers content, and defines font family.
  • .cards: Styles the container for cards, setting up flex display and spacing.
  • .card: Styles individual cards, defining dimensions, position, border radius, and background color.
  • .delete-btn, .add-btn: Styles the buttons for deleting and adding cards, setting dimensions, background, and cursor.
  • .sr-only: Hides elements visually but keeps them accessible for screen readers.
  • footer: Styles the footer, setting text alignment, font style, and line height.

7. @layer warning: This layer defines styles for warning messages.

  • .warning: Styles warning messages, setting padding, margin, border, background, and text alignment.
  • .warning > :first-child, .warning > :last-child: Styles first and last child elements of warning messages, adjusting margins.
  • .warning a: Styles links within warning messages, setting color.
  • .warning--info, .warning--alarm: Additional styles for different types of warning messages, such as info and alarm.
@layer view-transitions {

	@layer no-root {
		:root {
			view-transition-name: none;
		}
		::view-transition {
			pointer-events: none;
		}
	}
	
	@layer reorder-cards {
		@supports (view-transition-class: card) {
			.warning {
				display: none;
			}
			
			:root {
				--bounce-easing: linear(
					0, 0.004, 0.016, 0.035, 0.063, 0.098, 0.141 13.6%, 0.25, 0.391, 0.563, 0.765,
					1, 0.891 40.9%, 0.848, 0.813, 0.785, 0.766, 0.754, 0.75, 0.754, 0.766, 0.785,
					0.813, 0.848, 0.891 68.2%, 1 72.7%, 0.973, 0.953, 0.941, 0.938, 0.941, 0.953,
					0.973, 1, 0.988, 0.984, 0.988, 1
				);
			}

			.card {
				view-transition-class: card;
			}
			
			::view-transition-group(*.card) {
				animation-timing-function: var(--bounce-easing);
				animation-duration: 0.5s;
			}
		}
	}

	@layer add-card {
		@keyframes animate-in {
			0% {
				opacity: 0;
				translate: 0 -200px;
			}
			100% {
				opacity: 1;
				translate: 0 0;
			}
		}

		::view-transition-new(targeted-card):only-child {
			animation: animate-in ease-in 0.25s;
		}
	}

	@layer remove-card {
		@keyframes animate-out {
			0% {
				opacity: 1;
				translate: 0 0;
			}
			100% {
				opacity: 0;
				translate: 0 -200px;
			}
		}

		::view-transition-old(targeted-card):only-child {
			animation: animate-out ease-out 0.5s;
		}
	}

}


@layer base {
	body {
		display: grid;
		height: 90dvh;
		place-items: center;
		padding: 2rem 0;
		font-family: system-ui, sans-serif;
	}

	.cards {
		padding: 0;
		display: flex;
		justify-content: center;
		width: 100%;
		gap: 2rem;
		padding: 1rem;
		flex-wrap: wrap;
	}

	.card {
		width: 100%;
		aspect-ratio: 2/3;
		display: block;
		position: relative;
		border-radius: 1rem;
		max-width: 220px;
		min-width: 100px;

		background-color: grey;
	}

	.delete-btn {
		--icon: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxzdmcgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiB3aWR0aD0iNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjx0aXRsZS8+PHBhdGggZD0iTTExMiwxMTJsMjAsMzIwYy45NSwxOC40OSwxNC40LDMyLDMyLDMySDM0OGMxNy42NywwLDMwLjg3LTEzLjUxLDMyLTMybDIwLTMyMCIgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzAwMDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLXdpZHRoOjMycHgiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiMwMDA7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjMycHgiIHgxPSI4MCIgeDI9IjQzMiIgeTE9IjExMiIgeTI9IjExMiIvPjxwYXRoIGQ9Ik0xOTIsMTEyVjcyaDBhMjMuOTMsMjMuOTMsMCwwLDEsMjQtMjRoODBhMjMuOTMsMjMuOTMsMCwwLDEsMjQsMjRoMHY0MCIgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzAwMDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLXdpZHRoOjMycHgiLz48bGluZSBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMDAwO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6MzJweCIgeDE9IjI1NiIgeDI9IjI1NiIgeTE9IjE3NiIgeTI9IjQwMCIvPjxsaW5lIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiMwMDA7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS13aWR0aDozMnB4IiB4MT0iMTg0IiB4Mj0iMTkyIiB5MT0iMTc2IiB5Mj0iNDAwIi8+PGxpbmUgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzAwMDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLXdpZHRoOjMycHgiIHgxPSIzMjgiIHgyPSIzMjAiIHkxPSIxNzYiIHkyPSI0MDAiLz48L3N2Zz4=);
		position: absolute;
		bottom: -0.75rem;
		right: -0.75rem;
		width: 3rem;
		height: 3rem;
		padding: 0.5rem;
		border: 4px solid;
		border-radius: 100%;
		background: aliceblue var(--icon) no-repeat 50% 50% / 70%;
		color: white;
		cursor: pointer;

		&:hover {
			background-color: orangered;
		}
	}

	.add-btn {
		--icon: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxzdmcgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiB3aWR0aD0iNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjx0aXRsZS8+PGxpbmUgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzAwMDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLXdpZHRoOjMycHgiIHgxPSIyNTYiIHgyPSIyNTYiIHkxPSIxMTIiIHkyPSI0MDAiLz48bGluZSBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMDAwO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6MzJweCIgeDE9IjQwMCIgeDI9IjExMiIgeTE9IjI1NiIgeTI9IjI1NiIvPjwvc3ZnPg==);
		width: 3rem;
		height: 3rem;
		padding: 0.5rem;
		border: 4px solid;
		border-radius: 100%;
		background: aliceblue var(--icon) no-repeat 50% 50% / 70%;
		color: white;
		cursor: pointer;

		&:hover {
			background-color: cornflowerblue;
		}
	}

	.sr-only {
		border: 0;
		clip: rect(1px, 1px, 1px, 1px);
		clip-path: inset(50%);
		height: 1px;
		margin: -1px;
		overflow: hidden;
		padding: 0;
		position: absolute;
		width: 1px;
		white-space: nowrap;
	}

	footer {
		text-align: center;
		font-style: italic;
		line-height: 1.42;
	}
}

@layer warning {
	.warning {
		box-sizing: border-box;
		padding: 1em;
		margin: 1em 0;
		border: 1px solid #ccc;
		background: rgba(255 255 205 / 0.8);
		text-align: center;
	}

	.warning > :first-child {
		margin-top: 0;
	}

	.warning > :last-child {
		margin-bottom: 0;
	}

	.warning a {
		color: blue;
	}
	.warning--info {
		border: 1px solid #123456;
		background: rgb(205 230 255 / 0.8);
	}
	.warning--alarm {
		border: 1px solid red;
		background: #ff000010;
	}
} 

Step 3 (JavaScript Code):

The most crucial step is implementing JavaScript to add or remove cards dynamically. We'll utilize event listeners and DOM manipulation techniques to achieve this functionality.

Let's break it down:

1. Event Listener for Card Deletion:

  • document.querySelector('.cards').addEventListener('click', e => {...}): This event listener is attached to the container element of the cards. It listens for clicks within the container.
  • if (e.target.classList.contains('delete-btn')) {...}: This condition checks if the clicked element has the class "delete-btn", indicating that it's a delete button for a card.
  • Inside the condition:
  • if (!document.startViewTransition) {...}: This checks if the browser supports view transitions. If not, it directly removes the parent element of the clicked button, which is the card.
  • Otherwise, it sets the view transition name for the parent element of the clicked button to 'targeted-card'. Then, it initiates a view transition using document.startViewTransition(). When the transition finishes, it removes the parent element of the clicked button.

2. Event Listener for Adding Card:

  • document.querySelector('.add-btn').addEventListener('click', async (e) => {...}): This event listener is attached to the "Add" button. It listens for clicks on the button.
  • Inside the event listener:
  • const template = document.getElementById('card');: It retrieves the template for a new card.
  • const $newCard = template.content.cloneNode(true);: It clones the content of the template, creating a new card element.
  • if (!document.startViewTransition) {...}: Similar to before, this checks if the browser supports view transitions. If not, it directly appends the new card to the container and returns.
  • Otherwise, it sets the view transition name for the new card and randomly generates a background color for it. Then, it starts a view transition, appending the new card to the container when the transition finishes.
  • await transition.finished;: This line waits for the view transition to finish before continuing. It ensures that the subsequent code executes after the transition completes.
  • Finally, it sets a unique view transition name for the newly added card.
document.querySelector('.cards').addEventListener('click', e => {
	if (e.target.classList.contains('delete-btn')) {

		if (!document.startViewTransition) {
			e.target.parentElement.remove();
			return;
		}

		e.target.parentElement.style.viewTransitionName = 'targeted-card';
		document.startViewTransition(() => {
			e.target.parentElement.remove();
		});
	}
})

document.querySelector('.add-btn').addEventListener('click', async (e) => {
	const template = document.getElementById('card');

	const $newCard = template.content.cloneNode(true);

	if (!document.startViewTransition) {
		document.querySelector('.cards').appendChild($newCard);
		return;
	}

	$newCard.firstElementChild.style.viewTransitionName = 'targeted-card';
	$newCard.firstElementChild.style.backgroundColor = `#${ Math.floor(Math.random()*16777215).toString(16)}`;
	const transition = document.startViewTransition(() => {
		document.querySelector('.cards').appendChild($newCard);
	});

	await transition.finished;

	const rand = window.performance.now().toString().replace('.', '_') + Math.floor(Math.random() * 1000);
	document.querySelector('.cards .card:last-child').style.viewTransitionName = `card-${rand}`;

});

Final Output:

Create Interactive Add Remove Cards with HTML, CSS, and JavaScript.gif

Conclusion:

In conclusion, mastering the creation of dynamic add/remove card functionalities using HTML, CSS, and JavaScript opens up a world of possibilities for enhancing your web projects. By following the steps outlined in this tutorial, you've learned how to seamlessly integrate interactive card layouts into your webpages, providing users with a more engaging and dynamic experience. Remember to experiment with different designs and functionalities to tailor your card layouts to suit your project's specific needs. With the knowledge gained from this guide, you're well-equipped to continue exploring and expanding your web development skills. Happy coding, and may your web projects thrive with creativity and interactivity!

Code by: Bramus

That’s a wrap!

I hope you enjoyed this post. Now, with these examples, you can create your own amazing page.

Did you like it? Let me know in the comments below 🔥 and you can support me by buying me a coffee.

And don’t forget to sign up to our email newsletter so you can get useful content like this sent right to your inbox!

Thanks!
Faraz 😊

End of the article

Subscribe to my Newsletter

Get the latest posts delivered right to your inbox


Latest Post