Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Login Form UI/UX
.wrap
h4 Log In
form#login
label(for="username") Username
input#username(type="text")
label(for="password") Password
input#password(type="password")
#submit
input#submit-button(type="submit" value="Log In")
label(for="submit-button") Log In
.dots
.dot
.dot
.dot
.message.remain.hidden <span class="lnr lnr-lock"></span> <span class="number">1</span> Attempts left
.message.lockout.hidden <span class="lnr lnr-history"></span> <span class="number">10</span> Minute lock!
a(href="http://nathan.tokyo" target="_blank").sig NATHAN.TOKYO

Login Form UI/UX

I tried to see what I could improve usability wise on the humble Login form. As much as possible I wanted to use visual cues to convey information simply.

A Pen by Nathan Taylor on CodePen.

License.

//FOCUS THE USERNAME FIELD ON PAGE LOAD
//Comment this out if you want to edit as its really frustrating
$(document).ready(function(){
//$('#username').focus();
});
//CATCH THE SUBMIT
$('#login').on('submit', function(e){
e.preventDefault();
e.stopPropagation();
//CHECK THAT FIELDS ARE FILLED
if ($("#username").val() != '' && $("#password").val() != '' ){
//SUBMIT BUTTON ANIMATION HANDLING
$('#submit').addClass('checking');
$('#submit input').attr('disabled','true');
//IMITATE LOADING TIME
//This would be replaced with ajax in production.
setTimeout(function(){
$('#submit').removeClass('checking');
clearPassword();
handleMessage();
}, 1000);
} else{
//SHAKE THE USERNAME AND PW FIELDS IF THEY ARE EMPTY ON SUBMIT
if ($("#username").val() != ''){
$("#password").addClass('shake');
$("#password").focus();
setTimeout(function(){
$("#password").removeClass('shake');
},440);
} else {
$("#username").addClass('shake');
$("#username").focus();
setTimeout(function(){
$("#username").removeClass('shake');
},440);
}
}
})
//HANDLE ANIMATION AND CLEARING OF INCORRECT PW
function clearPassword(){
$("#password").addClass('shake');
setTimeout(function(){
$("#password").val('');
$("#password").focus();
$("#password").removeClass('shake');
$('#submit input').removeAttr('disabled');
},440);
}
//STORE USERNAMES AND ATTEMPTS REMAINING
//Purley for display purposes, this would be handled on the server with ajax in production.
var log = {};
//CHECK THE NUMBER OF ATTEMPTS REMANING AND SHOW APPROPRIATE MESSAGE
function handleMessage(){
var username = $('#username').val();
if(log[username] == undefined){
log[username] = 3;
}
var attempts = log[username];
switch(attempts){
case 3:
$('.remain').removeClass('hidden finished');
$('.number').removeClass('two one');
$('.lockout').addClass('hidden');
log[username]--
break;
case 2:
$('.remain').removeClass('hidden finished');
$('.number').addClass('two');
$('.number').removeClass('one');
$('.lockout').addClass('hidden');
log[username]--
break;
case 1:
$('.remain').removeClass('hidden finished');
$('.number').addClass('two one');
$('.lockout').addClass('hidden');
log[username]--
break;
case 0:
$('.remain').removeClass('hidden');
$('.remain').addClass('finished');
$('.number').addClass('two one');
$('.lockout').removeClass('hidden');
break;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
//COLOR CALCS
$hue: 5;
$shift: 30;
$color: hsl($hue,90%,55%);
$light: desaturate(lighten(adjust-hue($color,$shift), 20%), 40%);
$lighter: lighten(adjust-hue($color,$shift*2), 40%);
$lightest: lighten(adjust-hue($color,$shift*3), 44%);
$dark: darken(adjust-hue($color,-$shift), 10%);
$darker: desaturate(darken(adjust-hue($color,-$shift*2), 15%), 50%);
//FONTS
$sans: 'Source Sans Pro', sans-serif;
$serif: 'Bitter', serif;
//BASE REM SIZING
$base: 10px;
//TRANSTION VARS
$time: 220ms;
$ease: cubic-bezier(0.645, 0.045, 0.355, 1.000);
$ease-in: cubic-bezier(0.550, 0.055, 0.675, 0.190);
$ease-out: cubic-bezier(0.215, 0.610, 0.355, 1.000);
//ABOSOLUTLEY FABULOUS (THE MOST USED MIXIN EVER)
@mixin abfab{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
html{
font-size: $base
}
//BODY BACKGROUND
body{
overflow: hidden;
height: 100vh;
width: 100vw;
background: linear-gradient(0deg, $dark, $light);
font-family: $sans;
font-size: 2rem;
line-height: 1.414;
&:after{
z-index: 1;
content: '';
mix-blend-mode: color;
@include abfab;
background-image: radial-gradient( circle, transparent 40%, $darker );
transform-origin: 70% 100%;
transform: scaleY(2) scaleX(1.5);
}
}
h1,h2,h3,h4,h5,h6{
font-family: $serif;
margin: 0 0 0.8em 0;
font-weight: 800;
font-size: 1.414em;
line-height: 1;
}
*{
box-sizing: border-box;
}
.wrap{
position: absolute;
z-index: 2;
width: 420px;
top: 50%;
left: 50%;
transform: translate3d(-50%,-50%,0);
background: $lightest;
border-radius: 1.5rem;
padding: 5rem;
box-shadow: 1rem 2rem 4rem rgba($darker, 0.2),
inset 0.4rem 0.6rem 0.6rem rgba($light, 0.4),
inset -0.4rem -0.6rem 0.6rem rgba($dark, 0.2);
color: $darker;
label{
display: block;
margin: 2rem 0 0.5rem;
font-size: 2rem;
font-weight: 300;
}
input{
border-radius: 0.5rem;
padding: 1rem;
border: none;
outline: none;
font-family: $sans;
&[type="text"], &[type="password"]{
color: $darker;
width: 100%;
margin: 0.5rem 0;
background: hsl(185,100%,98%);
font-weight: 300;
box-shadow: inset -0.15rem -0.25rem 0.5rem rgba($lighter, 0.4),
inset 0.05rem 0.1rem 0.3rem rgba($darker, 0.4),
0.2rem 0.3rem 0.6rem rgba($lighter, 1),
-0.2rem -0.3rem 0.2rem rgba($dark, 0.1);
transition: color $time $ease-out, filter $time/2 $ease, transform $time*10 $ease-out;
@keyframes shake{
0%{
transform: translateX(0);
animation-timing-function: $ease-out;
}
30%{
transform: translateX(-1rem);
animation-timing-function: $ease-out;
}
40%{
transform: translateX(1rem);
animation-timing-function: $ease;
}
55%{
transform: translateX(-1rem);
animation-timing-function: $ease;
}
70%{
transform: translateX(0.7rem);
animation-timing-function: $ease;
}
85%{
transform: translateX(-0.7rem);
animation-timing-function: $ease-out;
}
100%{
transform: translateX(0);
}
}
&.shake{
filter: blur(0.6px);
color: transparent;
animation: shake $time*2 infinite;
transform: translateX(-1rem);
}
}
&[type="password"]{
letter-spacing: 0.2rem;
}
&[type="submit"]{
padding: 1rem 2rem;
color: $dark;
background: $dark;
cursor: pointer;
transition: box-shadow $time $ease, transform $time $ease;
transform-origin: 50% 50%;
transform: scale(1);
box-shadow: 0.05rem 0.05rem 0.1rem rgba($darker, 0.2),
inset 0.15rem 0.25rem 0.5rem rgba($lighter, 0.4),
inset -0.15rem -0.25rem 0.5rem rgba($darker, 0.4),
-0.2rem -0.3rem 0.6rem rgba($lighter, 0.1),
0.2rem 0.3rem 0.6rem rgba($dark, 0.1);
&:hover:not(:active):not([disabled]), &:focus:not(:active):not([disabled]){
transform: scale(1.05);
box-shadow: 0.05rem 0.25rem 0.3rem rgba($darker, 0.2),
inset 0.15rem 0.25rem 0.5rem rgba($lighter, 0.4),
inset -0.15rem -0.25rem 0.5rem rgba($darker, 0.4),
-0.2rem -0.3rem 0.6rem rgba($lighter, 0.1),
0.2rem 0.3rem 0.6rem rgba($dark, 0.1);
}
}
}
#submit{
margin-top: 2rem;
position: relative;
display: inline-block;
label{
@include abfab;
text-align: center;
line-height: 0;
color: white;
font-weight: 800;
text-shadow: 1px 1px 5px rgba($darker,0.3);
pointer-events: none;
transform-origin: 50% 30%;
transition: transform $time $ease, opacity $time $ease;
}
&:hover label{
transform: translateY(-10%) scale(1.05);
}
.dots{
@include abfab;
font-size: 2rem;
line-height: 1rem;
padding: 0;
pointer-events: none;
transform: translateY(-30%);
opacity: 0;
transition: transform $time $ease, opacity $time $ease;
.dot{
position: absolute;
top: calc(50% - 0.5rem);
width: 1rem;
height: 1rem;
border-radius: 1rem;
background: white;
display: block;
@keyframes bounce{
0%{ transform: translateY(0);
animation-timing-function: ease-out;
}
15%{ transform: translateY(-50%);
animation-timing-function: ease-in;
}
30%{ transform: translateY(0);}
100%{ transform: translateY(0);}
}
animation: bounce 800ms infinite;
&:nth-child(1){
left: calc(30% - 0.5rem);
animation-delay: 200ms;
}
&:nth-child(2){
left: calc(50% - 0.5rem);
animation-delay: 400ms;
}
&:nth-child(3){
left: calc(70% - 0.5rem);
animation-delay: 600ms;
}
}
}
&.checking{
label{
transform: translateY(30%);
opacity: 0;
}
.dots{
transform: translateY(0%);
opacity: 1;
}
}
}
.message{
position: absolute;
bottom: 5rem;
right: 5rem;
color: $darker;
font-size: 20px;
line-height: 30px;
transition: opacity $time $ease, transform $time $ease;
&.lockout{
color: $dark;
}
&.hidden{
opacity: 0;
transform: translateY(50%);
}
&.finished{
opacity: 0;
transform: translateY(-50%);
}
.lnr{
color: $dark;
font-size: 30px;
}
.number{
font-size: 30px;
position: relative;
display: inline-block;
transform: translateY(100%);
color: transparent;
margin-right: 0.2rem;
transition: color $time $ease, transform $time $ease;
&:before{
content: '2';
position: absolute;
left: 0;
transform: translateY(0%);
color: transparent;
transition: color $time $ease, transform $time $ease;
}
&:after{
content: '3';
position: absolute;
left: 0;
transform: translateY(-100%);
color: $dark;
transition: color $time $ease, transform $time $ease;
}
&.two{
&:after{
color: transparent;
transform: translateY(-200%);
}
&:before{
color: $dark;
transform: translateY(-100%);
}
}
&.one{
color: $dark;
transform: translateY(0%);
&:before, &:after{
color: transparent;
transform: translateY(-100%);
}
}
}
}
}
.sig{
position: fixed;
bottom: 8px;
right: 8px;
text-decoration: none;
font-size: 12px;
font-weight: 100;
font-family: sans-serif;
color: rgba(255,255,255,0.8);
letter-spacing: 2px;
}
<link href="https://cdn.linearicons.com/free/1.0.0/icon-font.min.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment