For most web apps, gray buttons with black text is fine.  But if you take some time to jazz up your buttons a little, you are making your app a little nicer to use and giving your users small visual cues to let them know their options.

Going from this:

image

to this:

image

image

with hover colors of green and red, which, in the U.S. at least, usually mean “go” and “stop”, can be just enough of a nudge to help your users and make them just a little happier.  Here’s how I built these.

Credit

The bulk of the concept and CSS work for this came from someone else, but I can’t remember who anymore.  It’s been over a year since I started using this technique.  It might be from an old version of HTML5 Boilerplate, Blueprint CSS, 960 Grid System, or something Rob Conery did on TekPub.  I wish I could give more specific attribution, but it was probably one of these.  My main contributions have been some CSS tweaks, the JavaScript, and the MVC Razor helpers.

HTML and images

This is what we’re going for as the emitted HTML.  We want these “buttons” to really be hyperlinks, not HTML buttons, so we don’t have as much browser style sheet nonsense getting in the way.

This does add some complexity for submitting, which has to be done with JavaScript now, but the same script (see below) can be used throughout the app.

The positive CSS class gives the green hover effect, and the negative one gives the red effect.

<div class="buttons">
    <a href="#" class="button positive">
        <img width="16" height="16" src="/Content/Images/Icons/tick.png" alt="">
        Save
    </a>
    <a href="/Home" class="button negative">
        <img width="16" height="16" src="/Content/Images/Icons/cross.png" alt="">
        Cancel
    </a>
</div>

The icons I used are from the Silk Icons collection.  There are lots of great icon libraries out there, so use your favorite.  Anything 16x16 should work, but you can go bigger or smaller and adjust the CSS and HtmlHelper tags.

JavaScript/jQuery

An anchor tag with a href to “#” won’t get your values submitted back to the server, so we’ll use some JavaScript and a touch of jQuery for that.  You’ll want to include this script on every page where you’re using these buttons.

$(function () {
    global.initButtons();
});

var global = {
    initButtons: function () {
        $('a.button.positive').click(function (e) {
            e.preventDefault();
            var parentForm = $(this).closest('form');
            if (!parentForm.valid()) {
                global.flashMessage('Error validating these values', 'error');
            }
            else {
                $(this).css(global.disableCss).unbind('click');
                parentForm.submit();
            }
        });
    },
    disableCss: { opacity: 0.5, display: 'inline-block' }
};

CSS

The CSS for this is pretty simple.  The div.buttons puts the buttons on one line and give a little vertical space around them.  The .button and .button img styles do most of the work, with block styling, light and medium gray borders, a light gray background, some padding around the text and icon to fatten the button up a little, margins so there is space before the next adjacent button on the right, and a small shadow effect.

The :hover effects are where the color is flipped to darker gray, green, or red, with corresponding border colors.

/*--------------------------*/
/* Buttons
/*--------------------------*/
div.buttons
{
    display: inline-block;
    margin: 10px 0;
}
.button
{
    display: block;
    float: left;
    border-top: 1px solid #CCC;
    border-left: 1px solid #CCC;
    border-bottom: 1px solid #AAA;
    border-right: 1px solid #AAA;
    background-color: #E3E3E3;
    font-family: Verdana, Helvetica, sans-serif;
    font-size: 100%;
    line-height: 110%;
    text-decoration: none !important;
    color: #333 !important;
    margin: 0 15px 0 0;
    padding: 5px 10px 5px 7px;
    cursor: pointer;
    box-shadow: 1px 1px 2px #969696;
    -moz-box-shadow: 1px 1px 2px #969696; /* Firefox */
    -webkit-box-shadow: 1px 1px 2px #969696; /* Safari and Chrome */
}
.button img
{
    margin: 0 3px -3px 0 !important;
    padding: 0;
    border: none;
    width: 16px;
    height: 16px;
    float: none;
}

/*--------------------------*/
/* Button colors - Standard
/*--------------------------*/
.button:hover
{
    border: 1px solid #999;
    background-color: #D6D6D6;
    color: #333 !important;
}

/*--------------------------*/
/* Button colors - Positive
/*--------------------------*/
.positive:hover
{
    background-color: #E6EFC2;
    border: 1px solid #C6D880;
    color: #264409 !important;
}

/*--------------------------*/
/* Button colors - Negative
/*--------------------------*/
.negative:hover
{
    background-color: #FBE3E4;
    border: 1px solid #FBC2C4;
    color: #8A1F11 !important;
}

MVC Razor HtmlHelpers

So now we know the HTML and CSS we want.  You could stop there and code your MVC views with that HTML.  But if you want your Razor views to be able to user simple helpers like this, keep reading.

<div class="buttons">
    @Html.LinkButtonForSave()
    @Html.LinkButtonForCancel()
</div>

The helpers here have two public entry points, LinkButtonForSave and LinkButtonForCancel. The rest of the code is refactoring out some of the route parsing and tag building work.  It’s probably overkill for just these two methods, but it makes sense when you keep adding helpers for other buttons, like add, edit, delete, confirm, print, etc.

using System.Web;
using System.Web.Mvc;

namespace Extensions
{
    public static class ButtonHelpers
    {
        public static MvcHtmlString LinkButtonForSave(this HtmlHelper helper)
        {
            var imageTag = ImageTag("tick.png");
            var anchorHtml = FormSubmitAnchorTag(imageTag, "Save");
            return MvcHtmlString.Create(anchorHtml);
        }
        
        public static MvcHtmlString LinkButtonForCancel(this HtmlHelper helper)
        {
            var areaName = AreaName(helper);
            var controllerName = ControllerName(helper);
            var imageTag = ImageTag("cross.png");
            var anchorHtml = AnchorTagWithImageAndText(areaName, controllerName, "Index", null, "button negative", imageTag, "Cancel");
            return MvcHtmlString.Create(anchorHtml);
        }

        private static TagBuilder ImageTag(string iconName)
        {
            var imageTag = new TagBuilder("img");
            imageTag.MergeAttribute("src", VirtualPathUtility.ToAbsolute(string.Format("~/Content/Images/Icons/{0}", iconName)));
            imageTag.MergeAttribute("width", "16");
            imageTag.MergeAttribute("height", "16");
            imageTag.MergeAttribute("alt", "");
            return imageTag;
        }

        private static string AreaName(HtmlHelper helper)
        {
            var routeData = helper.ViewContext.RouteData;
            return routeData.DataTokens["area"] == null ? string.Empty : routeData.DataTokens["area"].ToString();
        }

        private static string ControllerName(HtmlHelper helper)
        {
            var routeData = helper.ViewContext.RouteData;
            return routeData.GetRequiredString("controller");
        }

        private static string FormSubmitAnchorTag(TagBuilder imageTag, string anchorText)
        {
            var anchorTag = new TagBuilder("a");
            anchorTag.MergeAttribute("href", "#");
            anchorTag.AddCssClass("button positive");
            anchorTag.InnerHtml = imageTag.ToString(TagRenderMode.SelfClosing) + anchorText;
            return anchorTag.ToString();
        }

        private static string AnchorTagWithImageAndText(string areaName, string controllerName, string actionName, int? id, string cssClass, TagBuilder imageTag, string anchorText)
        {
            var anchorTag = new TagBuilder("a");
            if (areaName != string.Empty)
            {
                anchorTag.MergeAttribute("href", id == null
                                                     ? string.Format("/{0}/{1}/{2}", areaName, controllerName, actionName)
                                                     : string.Format("/{0}/{1}/{2}/{3}", areaName, controllerName, actionName, id));               
            }
            else
            {
                anchorTag.MergeAttribute("href", id == null
                                                     ? string.Format("/{0}/{1}", controllerName, actionName)
                                                     : string.Format("/{0}/{1}/{2}", controllerName, actionName, id));
            }
            anchorTag.AddCssClass(cssClass);
            anchorTag.InnerHtml = imageTag.ToString(TagRenderMode.SelfClosing) + anchorText;
            return anchorTag.ToString();
        }
    }
}