Let’s create an Animated Credit Card using HTML, CSS, and JavaScript! 💳✨ This interactive project mimics a real credit card that flips and updates live as users enter card details – perfect for practicing UI/UX and animation skills.
We’ll use:
- HTML to structure the front and back of the credit card.
- CSS for stylish design, layout, and smooth flip animations.
- JavaScript to dynamically update the card number, name, expiry, and CVV in real time.
Whether you’re new to web development or want to sharpen your interactive UI skills, this project is a fun and modern way to learn!
HTML :
This HTML code builds a responsive, animated credit card form using Vue.js. It visually displays a credit card that updates in real time as the user fills out fields like card number, holder name, expiry date, and CVV. The card flips to the back when the CVV field is focused. It uses custom styles, Vue transitions for smooth animations, and input masks (vue-the-mask
) for proper formatting. The card design also changes based on the card type (Visa, MasterCard, etc.), and a randomly selected background image is used to style the card’s appearance.
<!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8"> <title>Credit Card Animation | @coding.stella</title> <link rel="stylesheet" href="https://public.codepenassets.com/css/reset-2.0.min.css"> <link rel="stylesheet" href="./style.css"> </head> <body> <div class="wrapper" id="app"> <div class="card-form"> <div class="card-list"> <div class="card-item" v-bind:class="{ '-active' : isCardFlipped }"> <div class="card-item__side -front"> <div class="card-item__focus" v-bind:class="{'-active' : focusElementStyle }" v-bind:style="focusElementStyle" ref="focusElement"></div> <div class="card-item__cover"> <img v-bind:src="'./assets/images/' + currentCardBackground + '.jpeg'" class="card-item__bg"> </div> <div class="card-item__wrapper"> <div class="card-item__top"> <img src="./assets/images/chip.png" class="card-item__chip"> <div class="card-item__type"> <transition name="slide-fade-up"> <img v-bind:src="'./assets/images/' + getCardType + '.png'" v-if="getCardType" v-bind:key="getCardType" alt="" class="card-item__typeImg"> </transition> </div> </div> <label for="cardNumber" class="card-item__number" ref="cardNumber"> <template v-if="getCardType === 'amex'"> <span v-for="(n, $index) in amexCardMask" :key="$index"> <transition name="slide-fade-up"> <div class="card-item__numberItem" v-if="$index > 4 && $index < 14 && cardNumber.length > $index && n.trim() !== ''" >*</div> <div class="card-item__numberItem" :class="{ '-active' : n.trim() === '' }" :key="$index" v-else-if="cardNumber.length > $index"> {{cardNumber[$index]}} </div> <div class="card-item__numberItem" :class="{ '-active' : n.trim() === '' }" v-else :key="$index + 1" >{{n}}</div> </transition> </span> </template> <template v-else> <span v-for="(n, $index) in otherCardMask" :key="$index"> <transition name="slide-fade-up"> <div class="card-item__numberItem" v-if="$index > 4 && $index < 15 && cardNumber.length > $index && n.trim() !== ''" >*</div> <div class="card-item__numberItem" :class="{ '-active' : n.trim() === '' }" :key="$index" v-else-if="cardNumber.length > $index"> {{cardNumber[$index]}} </div> <div class="card-item__numberItem" :class="{ '-active' : n.trim() === '' }" v-else :key="$index + 1" >{{n}}</div> </transition> </span> </template> </label> <div class="card-item__content"> <label for="cardName" class="card-item__info" ref="cardName"> <div class="card-item__holder">Card Holder</div> <transition name="slide-fade-up"> <div class="card-item__name" v-if="cardName.length" key="1"> <transition-group name="slide-fade-right"> <span class="card-item__nameItem" v-for="(n, $index) in cardName.replace(/\s\s+/g, ' ')" v-if="$index === $index" v-bind:key="$index + 1">{{n}}</span> </transition-group> </div> <div class="card-item__name" v-else key="2">Full Name</div> </transition> </label> <div class="card-item__date" ref="cardDate"> <label for="cardMonth" class="card-item__dateTitle">Expires</label> <label for="cardMonth" class="card-item__dateItem"> <transition name="slide-fade-up"> <span v-if="cardMonth" v-bind:key="cardMonth">{{cardMonth}}</span> <span v-else key="2">MM</span> </transition> </label> / <label for="cardYear" class="card-item__dateItem"> <transition name="slide-fade-up"> <span v-if="cardYear" v-bind:key="cardYear">{{String(cardYear).slice(2,4)}}</span> <span v-else key="2">YY</span> </transition> </label> </div> </div> </div> </div> <div class="card-item__side -back"> <div class="card-item__cover"> <img v-bind:src="'./assets/images/' + currentCardBackground + '.jpeg'" class="card-item__bg"> </div> <div class="card-item__band"></div> <div class="card-item__cvv"> <div class="card-item__cvvTitle">CVV</div> <div class="card-item__cvvBand"> <span v-for="(n, $index) in cardCvv" :key="$index"> * </span> </div> <div class="card-item__type"> <img v-bind:src="'./assets/images/' + getCardType + '.png'" v-if="getCardType" class="card-item__typeImg"> </div> </div> </div> </div> </div> <div class="card-form__inner"> <div class="card-input"> <label for="cardNumber" class="card-input__label">Card Number</label> <input type="text" id="cardNumber" class="card-input__input" v-mask="generateCardNumberMask" v-model="cardNumber" v-on:focus="focusInput" v-on:blur="blurInput" data-ref="cardNumber" autocomplete="off"> </div> <div class="card-input"> <label for="cardName" class="card-input__label">Card Holders</label> <input type="text" id="cardName" class="card-input__input" v-model="cardName" v-on:focus="focusInput" v-on:blur="blurInput" data-ref="cardName" autocomplete="off"> </div> <div class="card-form__row"> <div class="card-form__col"> <div class="card-form__group"> <label for="cardMonth" class="card-input__label">Expiration Date</label> <select class="card-input__input -select" id="cardMonth" v-model="cardMonth" v-on:focus="focusInput" v-on:blur="blurInput" data-ref="cardDate"> <option value="" disabled selected>Month</option> <option v-bind:value="n < 10 ? '0' + n : n" v-for="n in 12" v-bind:disabled="n < minCardMonth" v-bind:key="n"> {{n < 10 ? '0' + n : n}} </option> </select> <select class="card-input__input -select" id="cardYear" v-model="cardYear" v-on:focus="focusInput" v-on:blur="blurInput" data-ref="cardDate"> <option value="" disabled selected>Year</option> <option v-bind:value="$index + minCardYear" v-for="(n, $index) in 12" v-bind:key="n"> {{$index + minCardYear}} </option> </select> </div> </div> <div class="card-form__col -cvv"> <div class="card-input"> <label for="cardCvv" class="card-input__label">CVV</label> <input type="text" class="card-input__input" id="cardCvv" v-mask="'####'" maxlength="4" v-model="cardCvv" v-on:focus="flipCard(true)" v-on:blur="flipCard(false)" autocomplete="off"> </div> </div> </div> <button class="card-form__button"> Submit </button> </div> </div> </div> <script src='https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js'></script> <script src='https://unpkg.com/vue-the-mask@0.11.1/dist/vue-the-mask.js'></script><script src="./script.js"></script> </body> </html>
CSS :
This CSS code styles a credit card form interface with a modern, responsive design. It uses Google Fonts and a dark background theme, styles the form container with padding, shadows, and rounded corners, and handles responsive layouts for mobile devices. It also designs a realistic-looking credit card with flip animations for front and back, input fields with focus effects, and transitions for animations. Overall, it creates a visually rich, animated UI for entering and displaying credit card details.
@import url("https://fonts.googleapis.com/css?family=Source+Code+Pro:400,500,600,700|Source+Sans+Pro:400,600,700&display=swap"); body { background: #252432; font-family: "Source Sans Pro", sans-serif; font-size: 16px; } * { box-sizing: border-box; } *:focus { outline: none; } .wrapper { min-height: 100vh; display: flex; padding: 50px 15px; } @media screen and (max-width: 700px), (max-height: 500px) { .wrapper { flex-wrap: wrap; flex-direction: column; } } .card-form { max-width: 570px; margin: auto; width: 100%; } @media screen and (max-width: 576px) { .card-form { margin: 0 auto; } } .card-form__inner { background: #fff; box-shadow: 0 30px 60px 0 rgba(90, 116, 148, 0.4); border-radius: 10px; padding: 35px; padding-top: 180px; } @media screen and (max-width: 480px) { .card-form__inner { padding: 25px; padding-top: 165px; } } @media screen and (max-width: 360px) { .card-form__inner { padding: 15px; padding-top: 165px; } } .card-form__row { display: flex; align-items: flex-start; } @media screen and (max-width: 480px) { .card-form__row { flex-wrap: wrap; } } .card-form__col { flex: auto; margin-right: 35px; } .card-form__col:last-child { margin-right: 0; } @media screen and (max-width: 480px) { .card-form__col { margin-right: 0; flex: unset; width: 100%; margin-bottom: 20px; } .card-form__col:last-child { margin-bottom: 0; } } .card-form__col.-cvv { max-width: 150px; } @media screen and (max-width: 480px) { .card-form__col.-cvv { max-width: initial; } } .card-form__group { display: flex; align-items: flex-start; flex-wrap: wrap; } .card-form__group .card-input__input { flex: 1; margin-right: 15px; } .card-form__group .card-input__input:last-child { margin-right: 0; } .card-form__button { width: 100%; height: 55px; background: #2364d2; border: none; border-radius: 5px; font-size: 22px; font-weight: 500; font-family: "Source Sans Pro", sans-serif; box-shadow: 3px 10px 20px 0px rgba(35, 100, 210, 0.3); color: #fff; margin-top: 20px; cursor: pointer; } @media screen and (max-width: 480px) { .card-form__button { margin-top: 10px; } } .card-item { max-width: 430px; height: 270px; margin-left: auto; margin-right: auto; position: relative; z-index: 2; width: 100%; } @media screen and (max-width: 480px) { .card-item { max-width: 310px; height: 220px; width: 90%; } } @media screen and (max-width: 360px) { .card-item { height: 180px; } } .card-item.-active .card-item__side.-front { transform: perspective(1000px) rotateY(180deg) rotateX(0deg) rotateZ(0deg); } .card-item.-active .card-item__side.-back { transform: perspective(1000px) rotateY(0) rotateX(0deg) rotateZ(0deg); } .card-item__focus { position: absolute; z-index: 3; border-radius: 5px; left: 0; top: 0; width: 100%; height: 100%; transition: all 0.35s cubic-bezier(0.71, 0.03, 0.56, 0.85); opacity: 0; pointer-events: none; overflow: hidden; border: 2px solid rgba(255, 255, 255, 0.65); } .card-item__focus:after { content: ""; position: absolute; top: 0; left: 0; width: 100%; background: #08142f; height: 100%; border-radius: 5px; filter: blur(25px); opacity: 0.5; } .card-item__focus.-active { opacity: 1; } .card-item__side { border-radius: 15px; overflow: hidden; box-shadow: 0 20px 60px 0 rgba(14, 42, 90, 0.55); transform: perspective(2000px) rotateY(0deg) rotateX(0deg) rotate(0deg); transform-style: preserve-3d; transition: all 0.8s cubic-bezier(0.71, 0.03, 0.56, 0.85); backface-visibility: hidden; height: 100%; } .card-item__side.-back { position: absolute; top: 0; left: 0; width: 100%; transform: perspective(2000px) rotateY(-180deg) rotateX(0deg) rotate(0deg); z-index: 2; padding: 0; height: 100%; } .card-item__side.-back .card-item__cover { transform: rotateY(-180deg); } .card-item__bg { max-width: 100%; display: block; max-height: 100%; height: 100%; width: 100%; object-fit: cover; } .card-item__cover { height: 100%; background-color: #1c1d27; position: absolute; height: 100%; background-color: #1c1d27; left: 0; top: 0; width: 100%; border-radius: 15px; overflow: hidden; } .card-item__cover:after { content: ""; position: absolute; left: 0; top: 0; width: 100%; height: 100%; background: rgba(6, 2, 29, 0.45); } .card-item__top { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 40px; padding: 0 10px; } @media screen and (max-width: 480px) { .card-item__top { margin-bottom: 25px; } } @media screen and (max-width: 360px) { .card-item__top { margin-bottom: 15px; } } .card-item__chip { width: 60px; } @media screen and (max-width: 480px) { .card-item__chip { width: 50px; } } @media screen and (max-width: 360px) { .card-item__chip { width: 40px; } } .card-item__type { height: 45px; position: relative; display: flex; justify-content: flex-end; max-width: 100px; margin-left: auto; width: 100%; } @media screen and (max-width: 480px) { .card-item__type { height: 40px; max-width: 90px; } } @media screen and (max-width: 360px) { .card-item__type { height: 30px; } } .card-item__typeImg { max-width: 100%; object-fit: contain; max-height: 100%; object-position: top right; } .card-item__info { color: #fff; width: 100%; max-width: calc(100% - 85px); padding: 10px 15px; font-weight: 500; display: block; cursor: pointer; } @media screen and (max-width: 480px) { .card-item__info { padding: 10px; } } .card-item__holder { opacity: 0.7; font-size: 13px; margin-bottom: 6px; } @media screen and (max-width: 480px) { .card-item__holder { font-size: 12px; margin-bottom: 5px; } } .card-item__wrapper { font-family: "Source Code Pro", monospace; padding: 25px 15px; position: relative; z-index: 4; height: 100%; text-shadow: 7px 6px 10px rgba(14, 42, 90, 0.8); user-select: none; } @media screen and (max-width: 480px) { .card-item__wrapper { padding: 20px 10px; } } .card-item__name { font-size: 18px; line-height: 1; white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis; text-transform: uppercase; } @media screen and (max-width: 480px) { .card-item__name { font-size: 16px; } } .card-item__nameItem { display: inline-block; min-width: 8px; position: relative; } .card-item__number { font-weight: 500; line-height: 1; color: #fff; font-size: 27px; margin-bottom: 35px; display: inline-block; padding: 10px 15px; cursor: pointer; } @media screen and (max-width: 480px) { .card-item__number { font-size: 21px; margin-bottom: 15px; padding: 10px 10px; } } @media screen and (max-width: 360px) { .card-item__number { font-size: 19px; margin-bottom: 10px; padding: 10px 10px; } } .card-item__numberItem { width: 16px; display: inline-block; } .card-item__numberItem.-active { width: 30px; } @media screen and (max-width: 480px) { .card-item__numberItem { width: 13px; } .card-item__numberItem.-active { width: 16px; } } @media screen and (max-width: 360px) { .card-item__numberItem { width: 12px; } .card-item__numberItem.-active { width: 8px; } } .card-item__content { color: #fff; display: flex; align-items: flex-start; } .card-item__date { flex-wrap: wrap; font-size: 18px; margin-left: auto; padding: 10px; display: inline-flex; width: 80px; white-space: nowrap; flex-shrink: 0; cursor: pointer; } @media screen and (max-width: 480px) { .card-item__date { font-size: 16px; } } .card-item__dateItem { position: relative; } .card-item__dateItem span { width: 22px; display: inline-block; } .card-item__dateTitle { opacity: 0.7; font-size: 13px; padding-bottom: 6px; width: 100%; } @media screen and (max-width: 480px) { .card-item__dateTitle { font-size: 12px; padding-bottom: 5px; } } .card-item__band { background: rgba(0, 0, 19, 0.8); width: 100%; height: 50px; margin-top: 30px; position: relative; z-index: 2; } @media screen and (max-width: 480px) { .card-item__band { margin-top: 20px; } } @media screen and (max-width: 360px) { .card-item__band { height: 40px; margin-top: 10px; } } .card-item__cvv { text-align: right; position: relative; z-index: 2; padding: 15px; } .card-item__cvv .card-item__type { opacity: 0.7; } @media screen and (max-width: 360px) { .card-item__cvv { padding: 10px 15px; } } .card-item__cvvTitle { padding-right: 10px; font-size: 15px; font-weight: 500; color: #fff; margin-bottom: 5px; } .card-item__cvvBand { height: 45px; background: #fff; margin-bottom: 30px; text-align: right; display: flex; align-items: center; justify-content: flex-end; padding-right: 10px; color: #1a3b5d; font-size: 18px; border-radius: 4px; box-shadow: 0px 10px 20px -7px rgba(32, 56, 117, 0.35); } @media screen and (max-width: 480px) { .card-item__cvvBand { height: 40px; margin-bottom: 20px; } } @media screen and (max-width: 360px) { .card-item__cvvBand { margin-bottom: 15px; } } .card-list { margin-bottom: -130px; } @media screen and (max-width: 480px) { .card-list { margin-bottom: -120px; } } .card-input { margin-bottom: 20px; } .card-input__label { font-size: 14px; margin-bottom: 5px; font-weight: 500; color: #1a3b5d; width: 100%; display: block; user-select: none; } .card-input__input { width: 100%; height: 50px; border-radius: 5px; box-shadow: none; border: 1px solid #ced6e0; transition: all 0.3s ease-in-out; font-size: 18px; padding: 5px 15px; background: none; color: #1a3b5d; font-family: "Source Sans Pro", sans-serif; } .card-input__input:hover, .card-input__input:focus { border-color: #3d9cff; } .card-input__input:focus { box-shadow: 0px 10px 20px -13px rgba(32, 56, 117, 0.35); } .card-input__input.-select { -webkit-appearance: none; background-image: url("/assets/images/dropdown.png"); background-size: 12px; background-position: 90% center; background-repeat: no-repeat; padding-right: 30px; } .slide-fade-up-enter-active { transition: all 0.25s ease-in-out; transition-delay: 0.1s; position: relative; } .slide-fade-up-leave-active { transition: all 0.25s ease-in-out; position: absolute; } .slide-fade-up-enter { opacity: 0; transform: translateY(15px); pointer-events: none; } .slide-fade-up-leave-to { opacity: 0; transform: translateY(-15px); pointer-events: none; } .slide-fade-right-enter-active { transition: all 0.25s ease-in-out; transition-delay: 0.1s; position: relative; } .slide-fade-right-leave-active { transition: all 0.25s ease-in-out; position: absolute; } .slide-fade-right-enter { opacity: 0; transform: translateX(10px) rotate(45deg); pointer-events: none; } .slide-fade-right-leave-to { opacity: 0; transform: translateX(-10px) rotate(45deg); pointer-events: none; }
JavaScript:
This Vue.js code creates an interactive credit card form. It manages data like card number, name, expiry, and CVV, and updates the card design based on the card type (Visa, MasterCard, etc.). When the component mounts, it focuses on the card number input. It also handles flipping the card for CVV input, shows the input highlight dynamically, and ensures valid month/year selection. The card number format adjusts based on the detected card type using regex checks.
new Vue({ el: "#app", data() { return { currentCardBackground: Math.floor(Math.random()* 25 + 1), // just for fun :D cardName: "", cardNumber: "", cardMonth: "", cardYear: "", cardCvv: "", minCardYear: new Date().getFullYear(), amexCardMask: "#### ###### #####", otherCardMask: "#### #### #### ####", cardNumberTemp: "", isCardFlipped: false, focusElementStyle: null, isInputFocused: false }; }, mounted() { this.cardNumberTemp = this.otherCardMask; document.getElementById("cardNumber").focus(); }, computed: { getCardType () { let number = this.cardNumber; let re = new RegExp("^4"); if (number.match(re) != null) return "visa"; re = new RegExp("^(34|37)"); if (number.match(re) != null) return "amex"; re = new RegExp("^5[1-5]"); if (number.match(re) != null) return "mastercard"; re = new RegExp("^6011"); if (number.match(re) != null) return "discover"; re = new RegExp('^9792') if (number.match(re) != null) return 'troy' return "visa"; // default type }, generateCardNumberMask () { return this.getCardType === "amex" ? this.amexCardMask : this.otherCardMask; }, minCardMonth () { if (this.cardYear === this.minCardYear) return new Date().getMonth() + 1; return 1; } }, watch: { cardYear () { if (this.cardMonth < this.minCardMonth) { this.cardMonth = ""; } } }, methods: { flipCard (status) { this.isCardFlipped = status; }, focusInput (e) { this.isInputFocused = true; let targetRef = e.target.dataset.ref; let target = this.$refs[targetRef]; this.focusElementStyle = { width: `${target.offsetWidth}px`, height: `${target.offsetHeight}px`, transform: `translateX(${target.offsetLeft}px) translateY(${target.offsetTop}px)` } }, blurInput() { let vm = this; setTimeout(() => { if (!vm.isInputFocused) { vm.focusElementStyle = null; } }, 300); vm.isInputFocused = false; } } });
Creating an Animated Credit Card with HTML, CSS, and JavaScript is an excellent way to practice form handling, animations, and real-time updates in the browser. It’s both practical and visually impressive – a perfect addition to your portfolio! 💡💻
If your project has problems, don’t worry. Just click to download the source code and face your coding challenges with excitement. Have fun coding!