Fade in border on hover

You need to provide a border by default to the hyperlink, which should be transparent in color. Later on hover, you may modify it's color. Leave the rest on the transition property.

See it yourself.

a { 
    border-bottom: 2px solid transparent;
    transition: border-bottom 1s;
    text-decoration: none;
}
a:hover {
    border-bottom-color: red;
}
<a href="#">Demo Hyperlink</a>

Cheers!


When an element has no border, then you add on hover you face a few issues such as page moving, drawing border from scratch etc

Solution: Try setting border to transparent first, so it's there but cannot be seen:

a { 
    border-bottom: 2px solid transparent; /* <- here */
    transition: border-bottom 1s;
    text-decoration: none; /* I added this for clarity of effect */
}
a:hover {
    border-bottom: 2px solid red;
}
<a href="">testing border</a>

Edit

Actually, you don't need to declare the whole border again, just change the color:

a:hover {
    border-color: red; /* <-- just change the color instead */
}

Why this happens

You get this because of your transition and the initial value of your element. All elements have default values, even when those aren't defined by you. For instance, <div> elements always have display: block per default and <b> elements have font-weight: bold per default.

Similarly, your <a> tag has border-bottom-color: rgb(0, 0, 0). This is true even when the thickness of the border is zero.

In chrome, you can see all of this in the "computed" section of the "Elements" tab:

qweqws

So when the transition starts, it's going to gradually change the color from black to the red you defined.


How to fix

What you need to do is to override that default values, with your own. This is to prevent it from starting off as black.

a{ 
    border-bottom: 2px solid transparent;
    transition: border-bottom 1s;
}
a:hover{
    border-bottom: 2px solid red;
}

A tip is to always define the same properties for an element "before" a hover and "during" one. Another thing you should be aware of is that using none as the initial value usually won't give you the behavior you want. Transitions need a numerical value as a start-off point (e.g 0).