Responsive CSS timeline

A client asked me to code a responsive CSS-only timeline, and so I did. The CSS might look a bit tedious at first but it really isn't. No rocket science here. You can see the whole thing in action on Codepen.

The markup is really basic. The ordered list of events asks for an <ol>. The <time> element holds the date.

<ol class="timeline">
    <li>
        <time>2015</time>
        <p>Bacon ipsum dolor amet cupim fatback strip steak beef ribs beef. Ham swine salami ribeye kielbasa, bacon boudin pork chop beef brisket fatback hamburger pancetta shoulder sirloin.</p>
    </li>
    <li>
        <time>2014</time>
        <p>Capicola chuck beef porchetta bresaola short ribs sausage ground round chicken shoulder jowl. Jowl prosciutto leberkas, pork picanha swine sirloin turducken salami venison.</p>
    </li>
    <li>
        <time>2013</time>
        <p>Meatball corned beef tri-tip, pork chop salami spare ribs chuck filet mignon ham meatloaf sausage.</p>
    </li>
    <li>
        <time>2012</time>
        <p> T-bone swine biltong leberkas jerky sirloin strip steak drumstick pig ham meatball frankfurter bresaola turducken.</p>
    </li>
    <li>
        <time>2011</time>
        <p>Flank filet mignon chicken, sausage drumstick doner hamburger swine tail cupim ham hock meatloaf biltong. Short ribs boudin chuck bresaola strip steak pastrami cow beef ribs porchetta tail rump filet mignon t-bone.</p>
    </li>
</ol>

The SCSS (Sass). Where I used padding for the odd/even styling because the events needed a background-color. The spine and the dots are all absolute positioned pseudo elements. Media queries are used to position both the line and dots.

.timeline {
    margin: 0;
    padding: 40px;
    list-style-type: none;
    font-family: Georgia;

    li {
        box-sizing: border-box;
        position: relative;
        background-color: #EDF5FF;
        padding: 30px 40px 30px 80px;
        border: 2px solid #EDF5FF;

        @media (min-width: 720px) {
            padding-left: 40px;
        }

        &:nth-child(2) {
            @media (min-width: 720px) {
                padding-top: 60px;
            }
        }

        &:nth-last-child(2) {
            @media (min-width: 720px) {
                padding-bottom: 60px;
            }
        }

        /* spine */
        &:after {
            content: '';
            position: absolute;
            top: 0;
            left: 30px;
            bottom: 0;
            width: 1px;
            background-color: #2175D9;

            @media (min-width: 720px) {
                left: 50%;
            }
        }

        /* both odds and evens, not header or footer */
        &:nth-child(even):not(:last-child),
        &:nth-child(odd):not(:first-child):not(:last-child) {
            border-top: 0;
            border-bottom: 0;

            @media (min-width: 720px) {
                border: 0;
            }

            &:after {
                left: 40px;

                @media (min-width: 720px) {
                    left: 50%;
                }
            }
        }

        /* evens, not header */
        &:nth-child(even):not(:last-child) {

            @media (min-width: 720px) {
                padding-right: calc(50% + 40px);
                text-align: right;

                time:before {
                    right: -50px;
                }
            }
        }

        /* odds, not footer */
        &:nth-child(odd):not(:first-child):not(:last-child) {

            @media (min-width: 720px) {
                padding-left: calc(50% + 40px);

                time:before {
                    left: -50px;
                }
            }
        }

        /* header and footer */
        &:first-child,
        &:last-child {
            margin: 0 10px;
            background-color: transparent;

            @media (min-width: 720px) {
                margin: 0 40px;
                text-align: center;
            }

            &:after {
                @media (min-width: 720px) {
                    height: 80px;
                }
            }

            time:before {
                left: -60px;
            }

            time {

                @media (min-width: 720px) {
                    position: static;
                }

                &:before {
                    width: 40px;
                    height: 40px;
                    left: -70px;

                    @media (min-width: 720px) {
                        top: auto;
                        left: 50%;
                        transform: translateX(-50%);
                    }
                }
            }
        }

        /* header */
        &:first-child {
            border-bottom: 0;

            @media (min-width: 720px) {
                padding-top: 50px;
                padding-bottom: 140px;
            }

            &:after {

                @media (min-width: 720px) {
                    top: auto;
                    bottom: 0;
                }
            }

            time:before {
                @media (min-width: 720px) {
                    bottom: 80px;
                }
            }
        }

        /* footer */
        &:last-child {
            border-top: 0;

            @media (min-width: 720px) {
                padding-top: 140px;
                padding-bottom: 50px;
            }

            time:before {

                @media (min-width: 720px) {
                    top: 80px;
                }
            }
        }

        /* dots */
        time {
            position: relative;
            color: #2175D9;
            font-size: 25px;

            &:before {
                content: '';
                transform: translateY(-50%);
                position: absolute;
                top: 50%;
                left: -50px;
                width: 20px;
                height: 20px;
                border-radius: 50%;
                background-color: #2175D9;

                @media (min-width: 720px) {
                    left: auto;
                }
            }
        }
    }
}

The result.

See the Pen Responsive CSS timeline by Stephan van Opstal (@stephanvanopstal) on CodePen.

comments powered by Disqus