Emoji Toggles!

Avatar of Chris Coyier
Chris Coyier on

UGURUS offers elite coaching and mentorship for agency owners looking to grow. Start with the free Agency Accelerator today.

Fun Friday thing! Natalya on CodePen made a super fun Pen.

You know how clicking a <label> with a for attribute that matches a checkbox input, it will toggle the input to checked or unchecked? That, combined with the :checked selector in CSS and the sibling combinator forms The Checkbox Hack, and you can use that to do all kinds of fun stuff.

Natalya used it to build a toggle switch where in the off/left position it shows one emoji, and it slides to the on/right position where it switches to a different emoji.

See the Pen Kitty Toggle by Natalya (@tallys) on CodePen.

I thought whittle it down a bit to the most basic level I could, so if you wanted to snag this idea for something you’re working on, you’d have a more clear building block.

See the Pen Emoji Toggles by Chris Coyier (@chriscoyier) on CodePen.

<!-- The wrap for everything, so you can position it wherever.
     Also, so all the other elements are siblings. -->
<div class="emoji-toggle emoji-travel">

  <!-- The input is first, so the ~ selector can select siblings after it. -->
  <input type="checkbox" id="toggle2" class="toggle">

  <!-- The emoji is a psuedo element on this. -->
  <div class="emoji"></div>

  <!-- This is absolutely positioned over everything.
       Also, the split/label comes from using both :before and :after -->
  <label for="toggle2" class="well"></label>


The input itself is visually hidden, but absolutely positioned on top of everything so that clicking pretty much anywhere will toggle the checkbox, and thus change the emoji and it’s position.

There is a bit more code than this, but these are the most vital bits of CSS:

.emoji-toggle {
  position: relative;
  .well { // the label
    cursor: pointer;
  .toggle { // the checkbox
    appearance: none;
    background: transparent;
    position: absolute;
    width: 100%;
    height: 100%;
    cursor: pointer;
    z-index: 100; 

    // "off"
    ~.emoji:before { 
      content: "emoji unicode here";
      position: absolute;
      left: 0;
      top: -15px;
      font-size: 40px;
      z-index: 1;
      transition: 0.2s;

    // "on"
    &:checked {
      ~.emoji:before {
        content: "different emoji unicode here";
        left: 100%;
        margin-left: -1em;


Sass for Versions

To make different versions of it, it required changing the pseudo element content on four different selectors with nested states. It was sorta mind-bending, so I made a quick Sass @mixin to help me. This is the kind of thing I love using preprocessors for – it’s not too fancy, it just helps abstract something that would be very wordy otherwise.

@mixin emojiType($leftEmoji, $rightEmoji, $leftLabel, $rightLabel) {
  .toggle {
    ~.emoji:before {
      content: $leftEmoji;
    &:checked {
      ~.emoji:before {
        content: $rightEmoji;
     ~label {
      &:before {
        content: $leftLabel;
      &:after {
        content: $rightLabel;

// Usage
.emoji-happy {
  @include emojiType(
    "\01F604", "\01F620", "Happy", "Mad"

Getting Unicode For Emojis

You may have noticed the unicode values for the emojis are like “\01F604” in the CSS content. Fortunately Tim Whitlock has a pretty solid reference page for this info.

So if the unicode for “grinning face” is “U+1F601”, in CSS pseudo content, that becomes “\01F601” because of how CSS escapes stuff.

Mileage My Vary

Emojis are weird. I’ve heard this doesn’t work correctly on all setups. Different browser/platform/versions have different emojis available. I’m sure none of us are surprised.