Using Custom Data Attributes and Pseudo-Elements

A tutorial on how to (ab)use custom data attributes and pseudo-elements for creating image captions.

PseudoElementsImageCaptions

In today’s tutorial I want to show you some simple CSS tricks using data attributes and pseudo-elements. The aim is to create an image caption using only an anchor and an image as markup. We’ll be exploring how to create pseudo-elements from some data attribute values and use them in a hover effect or simply show them next to the image. Using (more) semantic markup is of course what you should be doing, especially for data that is important to be shown. But this is a mere exercise for learning how to use those properties and how to create some nice effects.

If you’d like to explore some fancy image caption hover effects with “real” markup, check out the Caption Hover Effects.

And if you are interested in some really cool animations and transitions with pseudo-elements, then check out this article by Marco Barria: Examples of Pseudo-Elements Animations and Transitions.

The demos feature some beautiful artwork by Jaime Martinez.

Please note that we will be using some CSS features that might not work in older browsers. Animating pseudo-elements is still not supported by every browser.

Let’s start with the markup. As already mentioned, we’ll be using an image that is wrapped within an anchor:

<a 
	class="caption" 
	href="http://cargocollective.com/jaimemartinez/Illustration" 
	data-title="Smug Eagle" 
	data-description="I watched the four riders ...">

	<img src="images/1.jpg" alt="Illustration of Smug Eagle">
	
</a>

As you can see, we define two data attributes, one for the title and one for the description. The values of these attributes will be used in the pseudo elements :before and :after.

Our first image caption will be a static one: we want to place the content of the data attributes right next to the image. For that we’ll first style the anchor and the image:

.caption {
	display: inline-block;
	position: relative;
	margin: 10px;
}

.caption img {
	display: block;
	max-width: 100%;
}

The anchor will be displayed as an inline-block element, allowing content to flow around it. We add some margin and set the position to relative. This is important because we’ll position the pseudo-elements absolutely and this will allow us to do so relatively to the anchor.

Giving the image a max-width of 100% prepares it for a responsive environment.

Example 1: Caption next to image

Now, let’s make that first magic caption from the data-title and data-description values. We want both pseudo-elements to be absolutely positioned, on the right side next to the image. Let’s define some common styles for both:

.caption::before,
.caption::after {
	position: absolute;
	left: 100%;
	width: 90%;
	margin: 0 0 0 10%;
	font-weight: 300;
	color: #89867e;
}

By setting the left to 100%, we put the pseudo-elements right next to the image. 90% is relative to the width of our anchor which again is defined by its content, the image.

Let’s set the content for each pseudo-element. We’ll get the value of the respective data attribute and add it to the content of the pseudo-element by using attr():

.caption::before {
	content: attr(data-title);
	top: 0;
	height: 25%;
	padding: 5px 30px 15px 10px;
	font-size: 40px;
	border-bottom: 1px solid rgba(0,0,0,0.1);
}

Besides some dimension styling, we’ll do the same with the :after pseudo-element:

.caption::after {
	content: attr(data-description);
	top: 25%;
	padding: 20px 10px 0;
	font-size: 18px;
}

Check out the result of what we just did:

Example 2: Show on hover (opacity)

Now, let’s make a caption that shows on hover by simply animating the opacity of the pseudo-elements. For that we’ll set them to an absolute position again. But this time, they will cover the image. Setting the opacity to 0 and a transition for the opacity, will allow us to animate it on hover:

.caption::before,
.caption::after {
	opacity: 0;
	position: absolute;
	width: 100%;
	color: #fff;
	padding: 20px;
	transition: opacity 0.3s; 
}

The title will have a different background color than the description and we’ll restrict its height to 30% of the total height:

.caption::before {
	content: attr(data-title);
	top: 0;
	height: 30%;
	background: #a21f00;
	font-size: 40px;
	font-weight: 300;
}

For the description we will not simply add the data-description value, but we will prepend and append an opening and a closing quotation marks. For that we add the CSS values for the marks which we converted with a tool like the Entity Conversion Calculator by Evolution Technologies. The background will be slightly lighter to the one of the title and we will align the text to the right:

.caption::after {
	content: '201C' attr(data-description) '201D';
	top: 30%;
	height: 70%;
	background: #db2e00;
	font-size: 16px;
	text-align: right;
}

Since the title has a height of 30%, we will give the description the resting height of 70%.

Tiny break: 📬 Want to stay up to date with frontend and trends in web design? Subscribe and get our Collective newsletter twice a tweek.

And finally, we set the opacity to 1 on hover:

.caption:hover::before,
.caption:hover::after {
	opacity: 1;
}

The result of this style is the following (hover over the image):

Example 3: Slide in on hover (transform)

In the next example we want to show the caption with a bit more fanciness. On hover, we want it to slide in the title from the top and the description from the bottom. We also want to make the image darker to simulate a shadow. For that, we need to define some more things for the caption anchor and image.

The anchor’s overflow needs to be set to hidden because we need to position our title and description outside of it and we, of course, don’t want to see them. Additionally, we’ll give the anchor a black background so that we can darken the image when we reduce its opacity:

.caption {
	display: inline-block;
	position: relative;
	margin: 10px;
	overflow: hidden;
	background: #000;
}

We’ll add a transition to the image and set the opacity to 0.5 once we hover. This will make the image appear darker, just as we want:

.caption img {
	display: block;
	max-width: 100%;
	transition: opacity 0.3s ease-in-out; 
}

.caption:hover img {
	opacity: 0.5;
}

The common style of the ::before and ::after pseudo-elements is similar to the previous examples, just that we now give them an equal height and set the z-index to 1 so that they really stay on top of the image. The transition that we are adding will be for the transform. As you will see in the individual styles, we’ll position the pseudo-elements outside of the anchor rectangle by translating them on the Y-axis.

.caption::after,
.caption::before {
	position: absolute;
	width: 100%;
	height: 50%;
	color: #fff;
	z-index: 1;
	transition: transform 0.3s ease-in-out; 
}

Let’s set the background colors for the pseudo-elements and the transforms to move the title up and the description down:

.caption::after {
	content: attr(data-title);
	top: 0;
	background: #0083ab;
	font-size: 40px;
	font-weight: 300;
	padding: 30px;
	transform: translateY(-100%);
}

.caption::before {
	content: '...' attr(data-description) '...';
	top: 50%;
	background: #f27545;
	font-size: 14px;
	padding: 20px;
	transform: translateY(100%);
}

And on hover, we simply set the transform to translateY(0%) so that they go to the position we defined with the tops:

.caption:hover::after,
.caption:hover::before {
	transform: translateY(0%);
}

Example 4: Side push on hover (transform)

The last example that we want to do is a fancy “side push”. On hover, we want to move the image to the right while sliding in the pseudo-elements from the left, slightly shifted.

Let’s again create the shadow effect by animating the background of the anchor from a semi-transparent black to transparent:

.caption {
	display: inline-block;
	position: relative;
	margin: 10px;
	overflow: hidden;
	background: rgba(0,0,0,0.2);
	transition: background 0.3s ease-in-out;
}

.caption:hover {
	background: rgba(0,0,0,0);
}

The image will move to the right when we hover. For that, we’ll use translateX:

.caption img {
	display: block;
	max-width: 100%;
	transition: transform 0.3s ease-in-out;
}

.caption:hover img {
	transform: translateX(100%);
}

The pseudo-elements need to be beneath the anchor, so we’ll set a z-index of -1. The initial transform will set them to the left side:

.caption::before,
.caption::after {
	position: absolute;
	width: 100%;
	z-index: -1;
	background: #cecece;
	transform: translateX(-30%);
	transition: transform 0.3s ease-in-out;
}

As before, we will add the respective content to the pseudo-elements and give them a height and some styles for the text:

.caption::before {
	content: attr(data-title);
	height: 30%;
	color: #05b19a;
	font-size: 40px;
	font-weight: 300;
	padding: 30px;
}

.caption::after {
	content: '201C' attr(data-description) '201D';
	top: 30%;
	height: 70%;
	color: #fff;
	font-size: 14px;
	padding: 20px 30px;
}

And on hover, we’ll simply set the translateX to 0:

.caption:hover::before,
.caption:hover::after  {
	transform: translateX(0%);
}

See the result of this last example:

And that’s it! I hope you learned some new and interesting tricks, how to use pseudo-elements and how to give them the content of an attribute (think of others, not only custom data attributes) and how to animate them. Keep in mind that you should always add important content to the markup.

You can view all examples in this demo or download the ZIP file.

Please let me know if you enjoyed this tutorial and if would like to see more topics explored. Drop us a line if you have something specific in mind.

Manoela Ilic

Manoela is the main tinkerer at Codrops. With a background in coding and passion for all things design, she creates web experiments and keeps frontend professionals informed about the latest trends.

Stay in the loop: Get your dose of frontend twice a week

Fresh news, inspo, code demos, and UI animationsβ€”zero fluff, all quality. Make your Mondays and Thursdays creative!

Feedback 53

Comments are closed.
  1. Really not sure about this. What’s the point? There’s caption and all for that, why hide this content into data attributes?

    • Hi Laure, as I mention, this is an exercise for using data attributes and pseudo-elements, and not a recommendation on how to do your markup. Thanks for your feedback, cheers, ML

    • This would be useful in cutting down the amount of HTML tags used on portfolio. Instead of having an separate overlay div, you can easily use this technique to lessen the code.

    • I do see opportunities with this just as @FWPolice does… Thank you Mary, BTW I like your new picture a lot. πŸ˜‰

    • I can see many uses for displaying meta data that you wouldn’t normally consider content, or like many site’s are doing these days, displaying ad content without making it part of the page’s overall content.

    • Actually you can use data-* attributes for localization (there is a little bit different approach but anyway could be interesting)

  2. WOW That was really great article! and also thanks for sharing with us on facebook! now i use this on projects . πŸ˜‰
    Thanks Love you all

    • It’s merely, outside of the Transformation example (which you can check canIuse.com to see if the browsers you need to support actually supports transformation), just pseudo-elements and clever use of the content property.

      Those two things are supported by all major (desktop) browsers for quite some time.

  3. Nice article! Just wondering, what is the difference between using a single : for pseudo-element (a:before) and using two like you did (a::before) ?

    Thanks!

    • Always use a single, it’s more compatible and browsers that use :: will automatically create that from the one πŸ˜€
      you can check html5boilerplate CSS to confirm.

    • The double colon selector (a::before) is used to make an explicit distinction between pseudo-classes and pseudo-elements. So a:before is a pseudo-class and a::before is a pseudo-element.

      Robin

    • No difference really, just a convention to tell the difference between psuedo-elements and psuedo-classes.

    • This statement is misleading and contradictory: “No difference really, just a convention to tell the difference between psuedo-elements and psuedo-classes.”

      And this statement is incorrect: “…So a:before is a pseudo-class and a::before is a pseudo-element.”

      A single colon “:” is CSS2 syntax. A double colon “::” is CSS3 syntax.

      The first statement is misleading because conceptually there IS a difference, it just happens that browser vendors support single colon and double colon for pseudo-elements.

      Regarding the second statement, BOTH a:before and a::before ARE pseudo elements, the thing is that the pseudo-element using single colon (a:before) is, technically speaking, incorrect. It should use a double colon (a::before) because according to the spec ONLY pseudo-elements (::before, ::after). However, both forms are supported by modern browsers.

      Pseudo-classes are for example all states of a link: :link, :visited, :hover, :active, :focus (and it HAS to be in that order too).

      Pseudo-elements are, like in this tutorial: ::before, ::after, ::first-child, ::last-of-type, etc. But you can use a single colon on those (and any) pseudo-elements if you want as well, like so: :before, :after, :first-child, :last-of-type, etc.

      I personally never bother using double colons for pseudo-elements, I always use single colon for everything and I yet have to experience any issues.

    • It is correct that double-colon is what was recommended by W3C to use; however, like always, it’s up to the browser venders to adhere strictly to that or not at all.

      There’s versions of Chrome if I remember correctly that wouldn’t accept the use of double-colons for certain pseudo-elements and psuedo-classes to the extent it was just safe to use a single colon.

      Because of this, it seems you can essentially always be safe with one and expect some inconsistency with double colons from personal experience.

  4. Wow… I think that’s a great example to use in portfolio themes.
    Thanks for sharing with us!!!

  5. Loved the topic and how it was explained. Very useful in getting my head around some of the new CSS3 functionalities. Keep up the good work! πŸ™‚

  6. While I understand that this is a _demo_ and “not a tutorial how to markup” I find it annoying that we use CSS generated content with data attributes to simulate existing markup that is semantically valuable. FIGURE and FIGCAPTION does all this – it would make much more sense to use data attributes for things we do not have. I’ve written about data-attributes and their CSS interplay before – https://hacks.mozilla.org/2012/10/using-data-attributes-in-javascript-and-css/ and got some good feedback about performance issues. I think as tutorial writers we should not reinvent HTML, but extend it.

    • Interesting point, but I find it hard to understand how something like this would be “annoying” if there is great value in learning some useful properties, just the way I did. So as a tutorial writer, I would like to share that experience. Hope that makes sense and thanks for your feedback, cheers, ML

    • The annoying point is that this tutorial teaches people to create captions for images with generated CSS which is largely inaccessible to assistive technology. There is a perfectly valid HTML element which has semantic meaning and will be read by both assistive technology and search engines for that kind of job – figure and figcaption. Whilst the implementation is awesome and looks beautiful it teaches newcomers to re-invent instead of looking at the most obvious – and most supported – built-in implementation first.

      Let’s try this: would a with h1::after {content:attr(data-text)} be a good part of a tutorial?

      Just because we _can_ do things doesn’t mean they are a good plan. CSS generated content IMHO is very much under-used, especially seeing that it is backwards compatible to OldIE. It is nothing new at all though and there are incredibly useful examples out there – for example to show the href of links in a document when you print it (http://www.onextrapixel.com/2009/05/05/how-to-create-a-simple-print-css-for-your-site/).

    • While I completely agree that this is not the “right” markup, I must point out that, in my opinion, your conclusion that this tutorial teaches people to create captions for images with generated content is simply not correct. Instructive and informative lessons do not always have to be flagship examples of semantic markup. Showing what you can do, does help to understand properties from a different perspective. I’m more than clear about the fact that this is not the way to mark up things and I confide in the intelligence of the majority of readers that they understand this. But thank you for your opinion on this, I’ll definitely keep that in mind, and I’m looking forward to read more of your feedback in the future. Cheers, ML

    • Might be, but then I am worried about comments like the ones above claiming that this is good to “avoid an overlay DIV” and to “have less HTML in your portfolio”. I know people are lazy and are happy to copy and paste things that are beautiful. Therefore I don’t see how doing something knowingly wrong (or of lower quality) aids learning.

      That said, in personal trainings it makes sense, as you want the group to challenge what you say and come up with better solutions once they hit a snag. But that is a planned trap and should be followed up by explanations why the other way is better.

      I don’t think that this is dangerous, I find it just wasteful to not do something that can only be done with the certain technology that is meant to be introduced or explained. Right now content generated by CSS is used for something it should not be used for much like adding a CANVAS to the page just to show an image.

      I love what you do here, and how beautiful the examples are, hence I care to comment.

    • This is a great technical tutorial, hands down. Seriously.

      However, I have to agree strongly with Chris, but also add the question: What are the SEO implications of creating the content in this manner?

      I’m a well versed (I think) Web Designer/Front End Dev and it took me several minutes to figure out what was really going on with the content of these modules. I was expecting to see a simple , a heading like an and a <code>. When I realized the content was inside the <code><a> element.

      Which made me think: Ok, well, then are going to be PLENTY of conditions to use this: Legibility (can’t read content because it’s hidden), Usability (content can only be seen via a mouse event (what about touch devices?)), SEO (are search engines going to pick up the content?), Implementation (what if you want to have links inside the content?), etc.

      I’m also worried that people are just going to copy / paste this tutorial as is without questioning anything, because their assumptions could very well be something like “If this tutorial is featured here in codrops, then everything about it has to be 100% perfect”, when that might not necessarily be the case.

      The great value of this tutorial is exclusively the CSS IMHO.

    • Of course it worries me as well when I see comments like that, but it would be sad if I let an attitude like that confine how I write my tutorials because those people don’t even read them. We’ve featured many such approaches and I believe that it’s not a waste of time if it gives a bit more insight on those properties.
      Anyway, there surely are better examples and I’ll keep that in mind for future endeavors.

      @Ricardo, sorry about that, we’ll make it better. Code insertion is already easier (< and > don’t have to be replaced by < and >).

      Thanks and cheers,
      ML

    • Just want to say thanks to Codrops for making such great things. This article is here to present you some possibilities for using data attributes and pseudo-elements and nothing more, it isn’t tutorial to teach you how to do something right it is about the possibilities how you can create different things in my opinion. If you know how to do this better than write your own tutorial or improve this one. Keep up with the great work πŸ™‚

  7. Is there any fallback for internet explorer? That would be great!.. Nevertheless awesome exercise! πŸ™‚

  8. Hi, I was wondering, how you can achieve example 3 but with the opposite effect, like having the two slices opened and on hover to show the image.

  9. SEO wise this might not be advisable technique IMHO. But this is a great technique if used properly. Great article!

  10. Djeez the semantic SEO Police is at work again.

    Nice tutorial by the way!
    If something like `content: attr( data-title )` is annoying to you, then you really should look for another profession!

    It’s because articles like this that the web is moving forward!
    this article is about understanding what the possibilities are,
    being creative,
    looking at things from another perspective,
    …,
    it’s not about what semantic’s is.

  11. Great tutorial, really good resource for people getting stuck in to some more advanced css, just wish that some of our clients user based would allow us to implement with out requiring mass amounts of fallbacks.

  12. This is definitely amazing πŸ˜€

    Just found an attribute condensed way of arranging Example 3’s CSS

    .caption::before { content: attr(data-title); background: #0083ab; font-size: 40px; font-weight: 300; padding: 30px; } .caption::after, .caption:hover::before { -webkit-transform: translateY(0%); -moz-transform: translateY(0%); transform: translateY(0%); } .caption::after { content:'...' attr(data-description)'...'; background: #f27545; font-size: 14px; padding: 20px; } .caption::before, .caption:hover::after { -webkit-transform: translateY(-100%); -moz-transform: translateY(-100%); transform: translateY(-100%); }

  13. Fantastic post, learned a bunch from it! If you like the smell of pepper corns, do you know about Kampot peppers (from the Kampot region in Cambodia)? They’re out of this world.

  14. Hi, Here you provided example for text specification in data attribute.

    How it can be used for image url in the data attribute.

    Consider a case

    <div class="logo" data-href="https://www.google.co.in/images/srpr/logo11w.png">
    and the css code
    .logo { content: attr(data-href); }
    The above specification shows the image url as content. (but i need image to be displayed)
    I tried

    .logo { content: url(data-href); }

    In this case it shows image marker, not the actual image.

    http://jsfiddle.net/re8Ue/

  15. I’ve started coming across your work recently Mary Lou and am loving it so far. Please keep the good stuff coming!

  16. Great tutorial – am definitely going to find a way of including this in my next project πŸ™‚ Be great to link this up using touch through Modernizr and get it working on IOS as-well.

    Thanks!!

  17. On internet explorer I have display problems (really?!?). The texts – to which I gave the white color – are underlined … what could be the cause according to you? I tried to give a “text-underline: none! Important;” to the elements, but I have not solved anything. I have to face the obvious? -_-

  18. I was wondering in Example 2 or 3, is there a way on the hover to add an image with the description text? So you roll over the main image and the Hover shows an image next to the text description. Many thanks!