Swapping Out Text, Five Different Ways

Chris Coyier //

It's a common need in web apps: you click something and the text of the thing you just clicked changes. Perhaps something simple like a "Show" button that swaps to "Hide", or "Expand Description" to "Collapse Description." This is a fairly simple thing to do, but there are various considerations to make. Let's cover a bunch of ways.

jQuery Way (Less Markup / More JavaScript)

You need to store the "swap" text somewhere. I'd say in most cases it is a design/view concern so storing it in the markup is a good idea. We'll use the example of a button who's text swaps between "Hide" and "Show". A data-* attribute is a perfectly good place to store the swap text. So that becomes:

<button data-text-swap="Show">Hide</button>

It's easy to swap out the text, like:

var button = $("button");
button.text(button.data("text-swap"));

But, if we did that we'd lose the orignal text forever. We need to store the original text first. Another data-* attribute will do.

var button = $("button");
button.data("text-original", button.text());
button.text(button.data("text-swap"));

To do that on a click event, you'd do:

var button = $("button");
button.on("click", function() {
  button.data("text-original", button.text());
  button.text(button.data("text-swap"));
});

But that only goes one direction. To complete the "swap", we'll need to compare the current text value of the button to see if it matches the swap text or not. If it does, change it back to the original. If not, to the swap text. This is what it looks like all done:

$("button").on("click", function() {
  var el = $(this);
  if (el.text() == el.data("text-swap")) {
    el.text(el.data("text-original"));
  } else {
    el.data("text-original", el.text());
    el.text(el.data("text-swap"));
  }
});

jQuery Way (More Markup / Less JavaScript)

If we're willing to set that data-text-original value in the original markup, we can simplify the JavaScript a bit. We can use a single ternary operator to check if the swap matches the orignal and perform the right action based on the truthiness.

$("button").on("click", function() {
  var el = $(this);
  el.text() == el.data("text-swap") 
    ? el.text(el.data("text-original")) 
    : el.text(el.data("text-swap"));
});

Vanilla JavaScript Way

I'm guilty of using too much jQuery around here for things that can be done without it. This is what the first "less markup" version would look like in "raw" JavaScript:

var button = document.querySelectorAll("button")[0];
button.addEventListener('click', function() {
  if (button.getAttribute("data-text-swap") == button.innerHTML) {
    button.innerHTML = button.getAttribute("data-text-original");
  } else {
    button.setAttribute("data-text-original", button.innerHTML);
    button.innerHTML = button.getAttribute("data-text-swap");
  }
}, false);

CSS Way (with jQuery changing class names)

Since this is a view concern and could be considered a "state," a popular idea is to use JavaScript only to change classes which represent states and let CSS define what the visual change actually is.

We could use the class "on" to represent the swap state. Then that class would apply a pseudo element covering the old word and replacing it with the swap word. I don't think actual button elements with default browser styling take well to pseudo element so let's use an anchor here.

a {
  position: relative;
}
a.on:after {
  content: "Hide";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: white;
}

This is a bit funky, to be fair. I think this is almost worse that putting the swap word in the JavaScript. CSS isn't really meant for this kind of thing and likely has some accessibility concerns.

This also happens to work because the word "Hide" is smaller than "Show" a little bit. If the swap word was bigger, the original would stick out underneath the white cover. You might be able to get around that by inline-blocking the original, hiding the overflow, and kicking the original out of the box with text-indent. But the fact that the replacement word is absolutely positioned removes it from the flow, which could be an issue, not to mention real world design isn't always a simple as flat-color-on-flat-color.

CSS-Only Way

But hey as long as we're getting funky, we could use The Checkbox Hack here to make the text swap entirely CSS. The replacement happens the exact same way, it just happens when an invisible checkbox right before the word is either :checked or not. This means the word needs to be in a label as well, which is able to toggle that checkbox's state through the for attribute.

<input id="example-checkbox" type="checkbox">
<label for="example" id="example">Show</label>
#example {
  position: relative;
}
#example-checkbox {
  display: none;
}
#example-checkbox:checked + #example:after {
  content: "Hide";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: white;
}

Demo of All Five Ways

Check out this Pen!

More?

How have you done this kind of thing in the past? We didn't cover just putting the swap word right in the JavaScript... how do you feel about that?