179

I would like to have an alternative to a standard checkbox - basically I'd like to use images and when the user clicks the image, fade it out and overlay a tick box.

In essence, I want to do something like Recaptcha 2 does when it gets you to click images that meet a certain criteria. You can see a Recaptcha demo here but it might sometimes get you to solve text questions, as opposed to the image selection. So here's a screenshot:

Google Recaptcha screenshot

When you click one of the images (in this case, containing a picture of steak), the image you click shrinks in size and the blue tick appears, indicating that you've ticked it.

Let's say I want to reproduce this exact example.

I realise I can have 9 hidden checkboxes, and attach some jQuery so that when I click the image, it selects/deselects the hidden checkbox. But what about the shrinking of the image/overlaying the tick?

Cerbrus
  • 70,800
  • 18
  • 132
  • 147
bgs264
  • 4,572
  • 6
  • 38
  • 72
  • 2
    you can have two identical images: one with tick and other is without tick. And change images on click – user786 Jun 05 '15 at 09:58
  • 25
    @Alex That would be a particularly unflexible solution. – Siguza Jun 05 '15 at 09:59
  • @Siguza you need to explain what do u mean by "unflexibl solution"? – user786 Jun 05 '15 at 10:00
  • 3
    Have you tried adding/removing a CSS class on click that overrides the image size and does some `:before` magic for the tick image? – Hexaholic Jun 05 '15 at 10:00
  • @Hexaholic No, I didn't think about it, but that seems like a good solution. Thanks. – bgs264 Jun 05 '15 at 10:03
  • I've added an updated example to my answer http://stackoverflow.com/a/30664019/3296661 - You can tweak it to work how you want it to work. – YaBCK Jun 05 '15 at 10:39
  • 3
    @Alex "Unflexible" in the sense that it would require a lot more work to change anything, like adding a new picture to the collection or changing the "ticked" icon, when this would not be the case if the effect is programatically created at runtime. – Siguza Jun 05 '15 at 11:59
  • 2
    @Christian Replacing checkboxes with a custom something is a popular design decision (even if it's just a styled checkbox), and finding good/simple ways to do it is definitely of interest to devs who've had to deal with it. And this question (overlaying the checkbox replacement on something else) definitely makes it an interesting one. In particular, note how the accepted answer uses no JS at all, despite the apparent complexity of the task – Izkata Jun 05 '15 at 18:01
  • If you only want to use images (without checkboxes and labels etc.) I have added an additional jQuery solution. – jcuenod Jun 09 '15 at 21:03
  • 2
    How about this amazing(!) plugin: http://jcuenod.github.io/imgCheckbox/ ;) – jcuenod Jun 12 '15 at 23:04
  • 1
    @dippas: The duplicate closing is [discussed on meta](https://meta.stackoverflow.com/questions/381595/mark-as-duplicate-after-2-5-years) – BDL Mar 21 '19 at 10:14

8 Answers8

351

Pure semantic HTML/CSS solution

This is easy to implement on your own, no pre-made solution necessary. Also it will teach you a lot as you don't seem too easy with CSS.

This is what you need to do:

Your checkboxes need to have distinct id attributes. This allows you to connect a <label> to it, using the label's for-attribute.

Example:

<input type="checkbox" id="myCheckbox1" />
<label for="myCheckbox1"><img src="http://someurl" /></label>

Attaching the label to the checkbox will trigger a browser behaviour: Whenever someone clicks the label (or the image inside it), the checkbox will be toggled.

Next, you hide the checkbox by applying for example display: none; to it.

Now all that is left to do is set the style you want for your label::before pseudo element (which will be used as the visual checkbox replacement elements):

label::before {
    background-image: url(../path/to/unchecked.png);
}

In a last tricky step, you make use of CSS' :checked pseudo selector to change the image when the checkbox is checked:

:checked + label::before {
    background-image: url(../path/to/checked.png);
}

The + (adjacent sibling selector) makes sure you only change labels that directly follow the hidden checkbox in the markup.

You can optimize that by putting both images in a spritemap and only applying a change in background-position instead of swapping the image.

Of course you need to position the label correctly and apply display: block; and set correct width and height.

Edit:

The codepen example and snippet, which I created after these instructions, use the same technique, but instead of using images for the checkboxes, the checkbox replacements are done purely with CSS, creating a ::before on the label that, once checked, has content: "✓";. Add some rounded borders and sweet transitions and the result is really likable!

Here is a working codepen that showcases the technique and doesn't require images for the checkbox:

http://codepen.io/anon/pen/wadwpx

Here is the same code in a snippet:

ul {
  list-style-type: none;
}

li {
  display: inline-block;
}

input[type="checkbox"][id^="cb"] {
  display: none;
}

label {
  border: 1px solid #fff;
  padding: 10px;
  display: block;
  position: relative;
  margin: 10px;
  cursor: pointer;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

label::before {
  background-color: white;
  color: white;
  content: " ";
  display: block;
  border-radius: 50%;
  border: 1px solid grey;
  position: absolute;
  top: -5px;
  left: -5px;
  width: 25px;
  height: 25px;
  text-align: center;
  line-height: 28px;
  transition-duration: 0.4s;
  transform: scale(0);
}

label img {
  height: 100px;
  width: 100px;
  transition-duration: 0.2s;
  transform-origin: 50% 50%;
}

:checked+label {
  border-color: #ddd;
}

:checked+label::before {
  content: "✓";
  background-color: grey;
  transform: scale(1);
}

:checked+label img {
  transform: scale(0.9);
  box-shadow: 0 0 5px #333;
  z-index: -1;
}
<ul>
  <li><input type="checkbox" id="cb1" />
    <label for="cb1"><img src="https://picsum.photos/seed/1/100" /></label>
  </li>
  <li><input type="checkbox" id="cb2" />
    <label for="cb2"><img src="https://picsum.photos/seed/2/100" /></label>
  </li>
  <li><input type="checkbox" id="cb3" />
    <label for="cb3"><img src="https://picsum.photos/seed/3/100" /></label>
  </li>
  <li><input type="checkbox" id="cb4" />
    <label for="cb4"><img src="https://picsum.photos/seed/4/100" /></label>
  </li>
</ul>
Community
  • 1
  • 1
connexo
  • 53,704
  • 14
  • 91
  • 128
  • 34
    Glad you like it! Start fiddling around with CSS more, it's alot of fun and CSS can do so much more than you'd probably ever expect (except vertical centering, that's just too difficult ;) ). – connexo Jun 05 '15 at 10:42
  • 1
    Yeah, and understanding the way it works will make you think in the future, every time you want some DOM action on click. And you thought you can react only to `:hover` :) – connexo Jun 05 '15 at 10:44
  • 1
    That'll be my next thing to watch on Pluralsight - some CSS stuff :) – bgs264 Jun 05 '15 at 10:47
  • 6
    This is very nice! I noticed a small problem when you click the same label twice, it selects the image. This fixes it: http://codepen.io/anon/pen/jPmNjg – AKS Jun 05 '15 at 11:18
  • Just another application of the same technique: http://stackoverflow.com/questions/30664262/change-of-background-using-css/ – connexo Jun 05 '15 at 12:41
  • @AyeshK more helpful to just say use: `user-select: none;` – jcuenod Jun 05 '15 at 13:30
  • @connexo this is a great solution and I LOVE the end result. The thing I still don't like about your *answer* though is that you aren't explaining most of the code you end up with; your answer doesn't match your solution. – jcuenod Jun 05 '15 at 13:43
  • The linked codesnippet actually doesn't replace the picture like you proposed in your answer, but instead scales it to 0.9 and adds a checked icon to the label. The other diffrences are simple some styling, but you really should edit your answer to match your snippet (which is actually a prettier solution than two images). – Sebb Jun 05 '15 at 15:43
  • "This is easy to implement on your own and you'll learn a lot." *Proceeds to hand OP almost complete solution.* – FreeAsInBeer Jun 05 '15 at 16:54
  • Hey, this is awesome! Would've never thought about wrapping labels around images. – Christian Jun 05 '15 at 17:33
  • 1
    Tick would've looked better as SVG. CSS content puts you at the mercy of some terrible system fonts. – Adria Jun 06 '15 at 01:36
  • Nice solution, the only thing I would add would be to give the labels a common class and then use that to access them. This would make it modular and allow for traditional labels elsewhere on the same page. – MrMadsen Jun 10 '15 at 01:24
  • @MrMadsen Sure, but for a concise example, I like to omit stuff like that to keep the code short. – connexo Jun 10 '15 at 05:56
  • If you put your checkbox inside the label tag, you don't need `for` or a matching `id`. Example: ``. – rybo111 Jun 04 '16 at 08:37
  • @rybo111 This is trivial, but it wrecks the whole idea since in CSS you cannot select "upwards" this would not allow you to style the label differently depending on checked state. And no, you cannot use `:before` or `:after` on the checkbox itself since it cannot have `content`. – connexo Jun 04 '16 at 21:04
  • Since the label is just for click detection you don't need to style it. Just style a sibling to the input. – rybo111 Jun 05 '16 at 10:00
  • @rybo111 That would require additional, unnecessary markup. I like to have the markup as lean as possible. – connexo Jun 05 '16 at 16:39
  • The `img` elements inside the `label` elements need an `alt` attribute for accessibility reasons. Or you should provide an alternative CAPTCHA that does not rely on vision. – Tsundoku Nov 18 '16 at 19:49
  • @ChristopheStrobbe This was never meant to be the 100% everything-covered solution, it just demonstrates a technical solution for a given problem. And also, the question never was "how do I make images as checkboxes accessible". I see you care alot about accessibility, but it starts to get a missionary taste which I don't like. – connexo Nov 18 '16 at 22:38
  • lorempixel.com placeholders are not available. It's a possible dead service. I've updated the example using placekitten.com - https://codepen.io/naxa777/pen/QJGZbW – naXa stands with Ukraine Nov 12 '18 at 21:10
  • @naxa I'm not experiencing any downtimes so far on lorempixel service. – connexo Nov 13 '18 at 07:56
  • @connexo Is there a way to select ONLY one image at a time? – VijayRana Mar 20 '23 at 08:09
  • @VijayRana So that selecting a different image will deselect the previously selected one? – connexo Mar 20 '23 at 11:16
  • @connexo Yes, exactly. One selection at a time. – VijayRana Mar 20 '23 at 12:17
  • Replace `type="checkbox"` with `type="radio" name="foo"` on all `input`s, and you have just that. Also adjust `input[type="checkbox"]` in the CSS accordingly. – connexo Mar 21 '23 at 10:37
34

Pure CSS Solution

There are three neat devices invoked:

  1. The :checked selector
  2. The ::before pseudo-selector
  3. The css content property.

label:before {
  content: url("https://cdn1.iconfinder.com/data/icons/windows8_icons_iconpharm/26/unchecked_checkbox.png");
  position: absolute;
  z-index: 100;
}
:checked+label:before {
  content: url("https://cdn1.iconfinder.com/data/icons/windows8_icons_iconpharm/26/checked_checkbox.png");
}
input[type=checkbox] {
  display: none;
}
/*pure cosmetics:*/
img {
  width: 150px;
  height: 150px;
}
label {
  margin: 10px;
}
<input type="checkbox" id="myCheckbox1" />
<label for="myCheckbox1">
  <img src="https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcR0LkgDZRDTgnDrzhnXGDFRSItAzGCBEWEnkLMdnA_zkIH5Zg6oag">
</label>
<input type="checkbox" id="myCheckbox2" />
<label for="myCheckbox2">
  <img src="https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcRhJjGB3mQxjhI5lfS9SwXou06-2qT_0MjNAr0atu75trXIaR2d">
</label>
<input type="checkbox" id="myCheckbox3" />
<label for="myCheckbox3">
  <img src="https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcQuwWbUXC-lgzQHp-j1iw56PIgl_2eALrEENUP-ld72gq3s8cVo">
</label>
jcuenod
  • 55,835
  • 14
  • 65
  • 102
  • How it possible to add a radio button behavior to these checkboxes? I mean when you click on an image the already selected image become deselected. – Majid Shahabfar Jan 22 '18 at 08:41
  • Whoever asking himself the same question as @Libertad just change the inputs type to radio and give them all the same name "attribute" `` also change your css to hide the bullet : `input[type=radio]{display: none;}` – Lou May 15 '18 at 15:54
8

See this jQuery plugin: imgCheckbox (on npm and bower)

Disclaimer: No javascript is necessary to solve this problem. The tension is between maintainability and efficiency of code. While there's no need for a plugin (or any javascript), it sure does make it faster to build and often easier to change.

Barebones Solution:

With very simple HTML (none of the mess with checkboxes and labels etc.):

<img class="checkable" src="http://lorempixel.com/100/100" />

You can use jQuery's toggleClass to turn on/off a selected or checked class on the click event:

$("img.checkable").click(function () {
    $(this).toggleClass("checked");
});

Checked items are fetched with

$(".checked")

Plus Coolness:

You can style the images based off of this but a big problem is that without other DOM elements you can't even use ::before and ::after to add stuff like check marks. The solution is to wrap your images with another element (and it makes sense to attach the click listener to the wrapped element as well).

$("img.checkable").wrap("<span class='fancychecks'>")

This leaves your html really clean and your js incredibly readable. Take a look at the snippet...

/* Note that this js actually responds
   to a click event on the wrapped element!
   (not the image) */
$("img.checkable").wrap("<span class='fancychecks'>")
  .parent().click(function() {
    $(this).toggleClass("checked");
  });
/* style the images */
span.fancychecks img {
  display: block;
  margin: 0;
  padding: 0;
  transition-duration: 300ms;
  transform: scale(1);
  filter: none;
  -webkit-filter: grayscale(0);
}
span.fancychecks.checked img {
  transform: scale(0.8);
  filter: gray;
  filter: grayscale(1);
  -webkit-filter: grayscale(1);
}

/* style the parent spans */
span.fancychecks {
  padding: 0;
  margin: 5px;
  display: inline-block;
  border: 1px solid transparent;
  transition-duration: 300ms;
}
span.fancychecks.checked {
  border-color: #ccc;
}

/* Using conexo's fantastic CSS, make the checkmarks */
span.fancychecks::before {
  background-color: rgba(50, 200, 50, 0.7);
  color: white;
  content: "✓";
  font-weight: bold;
  border-radius: 50%;
  position: absolute;
  margin: 2px;
  top: 1;
  left: 1;
  z-index: 1;
  width: 25px;
  height: 25px;
  text-align: center;
  line-height: 28px;
  transform: scale(0);
  transition-duration: 300ms;
}
span.fancychecks.checked::before {
  transform: scale(1);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<img class="checkable" src="http://lorempixel.com/100/100/city/1" />
<img class="checkable" src="http://lorempixel.com/100/100/city/2" />
<img class="checkable" src="http://lorempixel.com/100/100/city/3" />

Using the imgCheckbox jQuery Plugin:

Inspired by the solution above, I have built a plugin which can be used as easily as:

$("img").imgCheckbox();
  • Injects the data for checked images into your form
  • Supports custom check marks
  • Supports customised CSS
  • Supports preselected elements
  • Supports radio groups instead of simple toggling of images
  • Has event callbacks
  • Sensible defaults
  • Lightweight and super easy to use

See it in action (and see the source)

jcuenod
  • 55,835
  • 14
  • 65
  • 102
  • I like this approach as well, but it has a number of downsides which are worth mentioning, the biggest being this: Assuming the user is meant to choose images, the information given by the user this way is surely meant to be processed somehow, be it via an AJAX call or via a form submit. For the pruposes of further processing of the information given by the user, having checkboxes where a number of choices have to be reflected and processed is just the natural, semantic way of doing it. – connexo Jun 12 '15 at 07:24
  • This is true, it's on my todo list for the plugin - to hijack form submission and inject the data into the form. But you're right, checkboxes are semantic for forms – jcuenod Jun 12 '15 at 07:48
  • I tried using your plugin, but if I'm adding more images after the initialization there doesn't seem to be a way to destroy the previous instance so that the new images can be included – Ian Kim Jun 05 '17 at 20:01
  • @IanKim interesting use case, can you explain more about what you're trying to do in an issue on github? https://github.com/jcuenod/imgCheckbox – jcuenod Aug 10 '17 at 10:01
  • It's an awesome plugin! please publish a Webjar =) – naXa stands with Ukraine Nov 12 '18 at 22:43
6

I would append an extra div with position: relative; and class="checked" which has the same width/height as the image has and than position in left: 0; top: 0; containing the icon. It starts with display: none;.

Now you can listen to the click-event:

$( '.captcha_images' ).click( function() {
    $(this + '.checked').css( 'display', 'block' );
    $(this).animate( { width: '70%', height: '70%' } );
});

This way you can get the icon and also resize the image to a smaller way.

Notice: Just wanted to show you the "logic" behind my thoughts, this example might not work or has some bugs in it.

Cagatay Ulubay
  • 2,491
  • 1
  • 14
  • 26
  • The technique I demonstrated is so simple and robust, it is even often applied in situations where you don't have any checkboxes semantically, like toggling the display of other elements and the like. **This** is the usecase where it is not only the easiest, most flexible solution, but also the semantically best. – connexo Jun 05 '15 at 10:40
  • 2
    Yes, you're right. At first I wanted to point out your first answer and improve it (before editing), because I like No-JS and nice Semantic ways, but than I read that the OP wants something in jQuery so I changed my answer. I remember where I got a downvote, because the OP didn't want a "better" or "the right way" solution, but the exact way he wrote. Maybe there is just something I overread or didn't understant or it depends on the OP? – Cagatay Ulubay Jun 05 '15 at 11:02
3

I've noticed other answers either don't use <label> (why not?), or require matching for and id attributes. This means if you have clashing IDs, your code won't work, and you must remember to make unique IDs each time.

Also, if you hide the input with display:none or visibility:hidden, the browser will not focus on it.

A checkbox and its text (or in this case, image) can be wrapped in a label:

.fancy-checkbox-label > input[type=checkbox] {
  position: absolute;
  opacity: 0;
  cursor: inherit;
}
.fancy-checkbox-label {
  font-weight: normal;
  cursor: pointer;
}
.fancy-checkbox:before {
  font-family: FontAwesome;
  content: "\f00c";
  background: #fff;
  color: transparent;
  border: 3px solid #ddd;
  border-radius: 3px;
  z-index: 1;
}
.fancy-checkbox-label:hover > .fancy-checkbox:before,
input:focus + .fancy-checkbox:before {
  border-color: #bdbdff;
}
.fancy-checkbox-label:hover > input:not(:checked) + .fancy-checkbox:before {
  color: #eee;
}
input:checked + .fancy-checkbox:before {
  color: #fff;
  background: #bdbdff;
  border-color: #bdbdff;
}
.fancy-checkbox-img:before {
  position: absolute;
  margin: 3px;
  line-height: normal;
}
input:checked + .fancy-checkbox-img + img {
  transform: scale(0.9);
  box-shadow: 0 0 5px #bdbdff;
}
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-T8Gy5hrqNKT+hzMclPo118YTQO6cYprQmhrYwIiQ/3axmI1hQomh7Ud2hPOy8SP1" crossorigin="anonymous">
<p>
  <label class="fancy-checkbox-label">
    <input type="checkbox">
    <span class="fancy-checkbox"></span>
    A normal checkbox
  </label>
</p>
<p>
  <label class="fancy-checkbox-label">
    <input type="checkbox">
    <span class="fancy-checkbox fancy-checkbox-img"></span>
    <img src="https://picsum.photos/seed/1/100">
  </label>
</p>
rybo111
  • 12,240
  • 4
  • 61
  • 70
  • This solution uses additional markup, which now thanks to the `:has()` pseudoselector function is no longer necessary. – connexo Oct 31 '22 at 14:36
  • I like your solution withour IDs and yust wrapping labels, but your image `http://placehold.it/150x150` can not be loaded what makes your example less impressiv because I cant see an example with custom images – Radon8472 Jan 17 '23 at 09:40
3

I made an example of "Angular - Use images like checkboxes and radios"

Link to Stackblitz

enter image description here

enter image description here

In app.component.html file:

<div class="cont-bg">
  <h5 class="text-white">Checkbox</h5>
  <div class="cont-main">
    <div class="cont-checkbox" *ngFor="let car of cars; index as i">
      <input type="checkbox" [id]="'myCheckbox-' + i" />
      <label [for]="'myCheckbox-' + i">
        <img [src]="car.img" />
        <span class="cover-checkbox">
          <svg viewBox="0 0 12 10">
            <polyline points="1.5 6 4.5 9 10.5 1"></polyline>
          </svg>
        </span>
        <div class="info">{{ car.name }}</div>
      </label>
    </div>
  </div>
  <h5 class="text-white">Radio</h5>
  <div class="cont-main">
    <div class="cont-checkbox" *ngFor="let car of cars; index as i">
      <input type="radio" name="myRadio" [id]="'myRadio-' + i" />
      <label [for]="'myRadio-' + i">
        <img [src]="car.img" />
        <span class="cover-checkbox">
          <svg viewBox="0 0 12 10">
            <polyline points="1.5 6 4.5 9 10.5 1"></polyline>
          </svg>
        </span>
        <div class="info">{{ car.name }}</div>
      </label>
    </div>
  </div>
</div>

In app.component.scss file:

* {
  font-family: Lato;
  --transition: 0.15s;
  --border-radius: 0.5rem;
  --background: #ffc107;
  --box-shadow: #ffc107;
  // --box-shadow: #0082ff;
}

.cont-bg {
  min-height: 100vh;
  background-image: radial-gradient(
    circle farthest-corner at 7.2% 13.6%,
    rgba(37, 249, 245, 1) 0%,
    rgba(8, 70, 218, 1) 90%
  );
  padding: 1rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.cont-main {
  display: flex;
  flex-wrap: wrap;
  align-content: center;
  justify-content: center;
}

.cont-checkbox {
  width: 150px;
  height: 100px;
  border-radius: var(--border-radius);
  box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
  transition: transform var(--transition);
  background: white;
  margin-bottom: 0.75rem;
  margin-right: 0.75rem;
  &:active {
    transform: scale(0.9);
  }
  input {
    display: none;
    &:checked + label {
      opacity: 1;
      box-shadow: 0 0 0 3px var(--background);
      .cover-checkbox {
        opacity: 1;
        transform: scale(1);
        svg {
          stroke-dashoffset: 0;
        }
      }
      img {
        -webkit-filter: none; /* Safari 6.0 - 9.0 */
        filter: none;
      }
    }
  }
  label {
    cursor: pointer;
    border-radius: var(--border-radius);
    overflow: hidden;
    width: 100%;
    height: 100%;
    position: relative;
    opacity: 0.6;
    img {
      width: 100%;
      height: 70%;
      object-fit: cover;
      clip-path: polygon(0% 0%, 100% 0, 100% 81%, 50% 100%, 0 81%);
      -webkit-filter: grayscale(100%); /* Safari 6.0 - 9.0 */
      filter: grayscale(100%);
    }
    .cover-checkbox {
      position: absolute;
      right: 5px;
      top: 3px;
      z-index: 1;
      width: 23px;
      height: 23px;
      border-radius: 50%;
      background: var(--box-shadow);
      border: 2px solid #fff;
      transition: transform var(--transition),
        opacity calc(var(--transition) * 1.2) linear;
      opacity: 0;
      transform: scale(0);
      svg {
        width: 13px;
        height: 11px;
        display: inline-block;
        vertical-align: top;
        fill: none;
        margin: 5px 0 0 3px;
        stroke: #fff;
        stroke-width: 2;
        stroke-linecap: round;
        stroke-linejoin: round;
        stroke-dasharray: 16px;
        transition: stroke-dashoffset 0.4s ease var(--transition);
        stroke-dashoffset: 16px;
      }
    }
    .info {
      text-align: center;
      margin-top: 0.2rem;
      font-weight: 600;
      font-size: 0.8rem;
    }
  }
}

In app.component.ts file:

import { Component, VERSION } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent {
  cars = [
    {
      id: '1',
      name: 'Mazda MX-5 Miata',
      img: 'https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/2021-mazda-mx-5-miata-mmp-1-1593459650.jpg?crop=0.781xw:0.739xh;0.109xw,0.0968xh&resize=480:*',
    },
    {
      id: '2',
      name: 'Toyota Supra',
      img: 'https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/2020-chevrolet-corvette-c8-102-1571146873.jpg?crop=0.548xw:0.411xh;0.255xw,0.321xh&resize=980:*',
    },
    {
      id: '3',
      name: 'Chevy Corvette',
      img: 'https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/2021-porsche-cayman-mmp-1-1593003674.jpg?crop=0.648xw:0.485xh;0.129xw,0.263xh&resize=980:*',
    },
    {
      id: '4',
      name: 'Porsche 718 Cayman',
      img: 'https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/2021-porsche-718-cayman-mmp-1-1593003156.jpg?crop=0.735xw:0.551xh;0.138xw,0.240xh&resize=980:*',
    },
    {
      id: '5',
      name: 'Porsche 718 Boxster',
      img: 'https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/2021-bmw-m2-mmp-1-1599687968.jpg?crop=0.706xw:0.528xh;0.102xw,0.268xh&resize=980:*',
    },
    {
      id: '6',
      name: 'BMW M2',
      img: 'https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/2021-bmw-z4-mmp-1-1599583762.jpg?crop=0.779xw:0.584xh;0.0782xw,0.196xh&resize=980:*',
    },
    {
      id: '7',
      name: 'BMW Z4',
      img: 'https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/2021-chevrolet-camaro-mmp-1-1585333717.jpg?crop=0.344xw:0.331xh;0.241xw,0.328xh&resize=980:*',
    },
    {
      id: '8',
      name: 'Chevy Camaro',
      img: 'https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/2021-chevrolet-camaro-zl1-mmp-1-1604071262.jpg?crop=0.818xw:0.663xh;0.0799xw,0.163xh&resize=980:*',
    },
    {
      id: '9',
      name: 'Chevy Camaro ZL1',
      img: 'https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/2021-chevrolet-camaro-zl1-mmp-1-1604071262.jpg?crop=0.818xw:0.663xh;0.0799xw,0.163xh&resize=768:*',
    },
  ];
}

Pure CSS Checkbox and Radio:

* {
  font-family: Lato;
  margin: 0;
  padding: 0;
  --transition: 0.15s;
  --border-radius: 0.5rem;
  --background: #ffc107;
  --box-shadow: #ffc107;
}

.cont-bg {
  min-height: 100vh;
  background-image: radial-gradient(
    circle farthest-corner at 7.2% 13.6%,
    rgba(37, 249, 245, 1) 0%,
    rgba(8, 70, 218, 1) 90%
  );
  padding: 1rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.cont-title {
  color: white;
  font-size: 1.25rem;
  font-weight: 600;
  margin-bottom: 1rem;
}

.cont-main {
  display: flex;
  flex-wrap: wrap;
  align-content: center;
  justify-content: center;
}

.cont-checkbox {
  width: 150px;
  height: 100px;
  border-radius: var(--border-radius);
  box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
  background: white;
  transition: transform var(--transition);
}

.cont-checkbox:first-of-type {
  margin-bottom: 0.75rem;
  margin-right: 0.75rem;
}

.cont-checkbox:active {
  transform: scale(0.9);
}

input {
  display: none;
}

input:checked + label {
  opacity: 1;
  box-shadow: 0 0 0 3px var(--background);
}

input:checked + label img {
  -webkit-filter: none; /* Safari 6.0 - 9.0 */
  filter: none;
}

input:checked + label .cover-checkbox {
  opacity: 1;
  transform: scale(1);
}

input:checked + label .cover-checkbox svg {
  stroke-dashoffset: 0;
}

label {
  display: inline-block;
  cursor: pointer;
  border-radius: var(--border-radius);
  overflow: hidden;
  width: 100%;
  height: 100%;
  position: relative;
  opacity: 0.6;
}

label img {
  width: 100%;
  height: 70%;
  object-fit: cover;
  clip-path: polygon(0% 0%, 100% 0, 100% 81%, 50% 100%, 0 81%);
  -webkit-filter: grayscale(100%); /* Safari 6.0 - 9.0 */
  filter: grayscale(100%);
}

label .cover-checkbox {
  position: absolute;
  right: 5px;
  top: 3px;
  z-index: 1;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--box-shadow);
  border: 2px solid #fff;
  transition: transform var(--transition),
    opacity calc(var(--transition) * 1.2) linear;
  opacity: 0;
  transform: scale(0);
}

label .cover-checkbox svg {
  width: 13px;
  height: 11px;
  display: inline-block;
  vertical-align: top;
  fill: none;
  margin: 5px 0 0 3px;
  stroke: #fff;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 16px;
  transition: stroke-dashoffset 0.4s ease var(--transition);
  stroke-dashoffset: 16px;
}

label .info {
  text-align: center;
  margin-top: 0.2rem;
  font-weight: 600;
  font-size: 0.8rem;
}
<div class="cont-bg">
  <div class="cont-title">Checkbox</div>
  <div class="cont-main">
    <div class="cont-checkbox">
      <input type="checkbox" id="myCheckbox-1" />
      <label for="myCheckbox-1">
        <img
          src="https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/2021-mazda-mx-5-miata-mmp-1-1593459650.jpg?crop=0.781xw:0.739xh;0.109xw,0.0968xh&resize=480:*"
        />
        <span class="cover-checkbox">
          <svg viewBox="0 0 12 10">
            <polyline points="1.5 6 4.5 9 10.5 1"></polyline>
          </svg>
        </span>
        <div class="info">Mazda MX-5 Miata</div>
      </label>
    </div>
    <div class="cont-checkbox">
      <input type="checkbox" id="myCheckbox-2" />
      <label for="myCheckbox-2">
        <img
          src="https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/2020-chevrolet-corvette-c8-102-1571146873.jpg?crop=0.548xw:0.411xh;0.255xw,0.321xh&resize=980:*"
        />
        <span class="cover-checkbox">
          <svg viewBox="0 0 12 10">
            <polyline points="1.5 6 4.5 9 10.5 1"></polyline>
          </svg>
        </span>
        <div class="info">Toyota Supra</div>
      </label>
    </div>
  </div>
  <div class="cont-title">Radio</div>
  <div class="cont-main">
    <div class="cont-checkbox">
      <input type="radio" name="myRadio" id="myRadio-1" />
      <label for="myRadio-1">
        <img
          src="https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/2021-mazda-mx-5-miata-mmp-1-1593459650.jpg?crop=0.781xw:0.739xh;0.109xw,0.0968xh&resize=480:*"
        />
        <span class="cover-checkbox">
          <svg viewBox="0 0 12 10">
            <polyline points="1.5 6 4.5 9 10.5 1"></polyline>
          </svg>
        </span>
        <div class="info">Mazda MX-5 Miata</div>
      </label>
    </div>
    <div class="cont-checkbox">
      <input type="radio" name="myRadio" id="myRadio-2" />
      <label for="myRadio-2">
        <img
          src="https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/2020-chevrolet-corvette-c8-102-1571146873.jpg?crop=0.548xw:0.411xh;0.255xw,0.321xh&resize=980:*"
        />
        <span class="cover-checkbox">
          <svg viewBox="0 0 12 10">
            <polyline points="1.5 6 4.5 9 10.5 1"></polyline>
          </svg>
        </span>
        <div class="info">Toyota Supra</div>
      </label>
    </div>
  </div>
</div>
Vương Hữu Thiện
  • 1,460
  • 14
  • 21
1

Here a quick example of selecting an image like a checkbox

Updated Example using Knockout.js:

var imageModel = function() {
    this.chk = ko.observableArray();
};
ko.applyBindings(new imageModel());
    input[type=checkbox] {
        display:none;
      }
 
  input[type=checkbox] + label
   {
       display:inline-block;
        width:150px;
        height:150px;
        background:#FBDFDA;
        border:none;
   }
   
   input[type=checkbox]:checked + label
    {
        background:#CFCFCF;
        border:none;
        position:relative;
        width:100px;
        height:100px;
        padding: 20px;
    }

   input[type=checkbox]:checked + label:after
    {
        content: '\2713';
        position:absolute;
        top:-10px;
        right:-10px;
        border-radius: 10px;
        width: 25px;
        height: 25px;
        border-color: white;
        background-color: blue;
    }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.0.0/knockout-min.js"></script>
<input type='checkbox' name='image1' value='image1' id="image1" data-bind="checked: chk"/><label for="image1"></label><label for="image1"><img class='testbtn'/></label>

<div data-bind="html: chk"></div>
YaBCK
  • 2,949
  • 4
  • 32
  • 61
0

To expand on the accepted answer for anyone using WordPress & GravityForms to generate their forms and wish to automatically populate checkbox fields with a list of posts and their associated Featured Thumbnail

// Change '2' to your form ID
add_filter( 'gform_pre_render_2', 'populate_checkbox' );
add_filter( 'gform_pre_validation_2', 'populate_checkbox' );
add_filter( 'gform_pre_submission_filter_2', 'populate_checkbox' );
add_filter( 'gform_admin_pre_render_2', 'populate_checkbox' );

function populate_checkbox( $form ) {

    foreach( $form['fields'] as &$field )  {

        // Change '41' to your checkbox field ID
        $field_id = 41;
        if ( $field->id != $field_id ) {
            continue;
        }

        // Adjust $args for your post type
        $args = array(
                'post_type' => 'pet',
                'post_status' => 'publish',
                'posts_per_page' => -1,
                'tax_query' => array(
                        array(
                                'taxonomy' => 'pet_category',
                                'field' => 'slug',
                                'terms' => 'cat'
                        )
                )
        );

        $posts = get_posts( $args );

        $input_id = 1;

        foreach( $posts as $post ) {

            $feat_image_url = wp_get_attachment_image( get_post_thumbnail_id( $post->ID ), 'thumbnail' );
            $feat_image_url .= '<br />' . $post->post_title;

            if ( $input_id % 10 == 0 ) {
                $input_id++;
            }

            $choices[] = array( 'text' => $feat_image_url, 'value' => $post->post_title );
            $inputs[] = array( 'label' => $post->post_title, 'id' => "{$field_id}.{$input_id}" );

            $input_id++;
        }

        $field->choices = $choices;
        $field->inputs = $inputs;

    }

    return $form;
}

And the CSS:

.gform_wrapper .gfield_checkbox li[class^="gchoice_2_41_"] {
    display: inline-block;
}

.gform_wrapper .gfield_checkbox li input[type="checkbox"][id^="choice_2_41_"] {
    display: none;
}


.gform_wrapper .gfield_checkbox li label[id^="label_2_41_"] {
    border: 1px solid #fff;
    padding: 10px;
    display: block;
    position: relative;
    margin: 10px;
    cursor: pointer;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

label[id^="label_2_41_"]:before {
    font-family: "font-icons";
    font-size: 32px;
    color: #1abc9c;
    content: " ";
    display: block;
    background-color: transparent;
    position: absolute;
    top: -5px;
    left: -5px;
    width: 25px;
    height: 25px;
    text-align: center;
    line-height: 28px;
    transition-duration: 0.4s;
    transform: scale(0);
}

label[id^="label_2_41_"] img {
    transition-duration: 0.2s;
    transform-origin: 50% 50%;
}

:checked + label[id^="label_2_41_"] {
    border-color: #ddd;
}

/* FontAwesome tick */
:checked + label[id^="label_2_41_"]:before {
    content: "\e6c8";
    background-color: transparent;
    transform: scale(1);
}

:checked + label[id^="label_2_41_"] img {
    transform: scale(0.9);
    box-shadow: 0 0 5px #333;
    z-index: 0;
}
essexboyracer
  • 358
  • 3
  • 17