Skip to content

Faking a placeholder Attribute for an Editable div, and Some CSS Tricks

HTML input elements have a placeholder attribute which you can use to show a bit of text to prompt the end user. Although you can make an editable div by using the contenteditable attribute, it will not support the placeholder attribute. I needed to do both, so I ended up reinventing the placeholder attribute for editable divs. Here’s how I did it.

I wanted the "placeholder" in the editable div to behave as much as possible like a "real" placeholder in an input element. So I started by making a list of requirements:

  1. The placeholder text should cosmetically resemble an input’s placeholder.
  2. All markup used should be valid.
  3. The placeholder text should not appear in the DOM.
  4. The placeholder text should disappear when the div contains any text or when it has input focus.

I decided to use a data-placeholder attribute in lieu of a placeholder attribute since this is valid for a div. In order to prevent the placeholder text from appearing in the DOM, I used the CSS :before pseudo-element to hold the text. Making the placeholder text disappear when the div has focus is easy, but making it disappear when the div contains text is harder, because the :contains pseudo-class doesn’t exist. So I had to use a bit of JavaScript and another data- attribute for that.

The markup for the div looks like this:

<div contenteditable='true' class='editable' data-placeholder='Enter some text'></div>

Here’s the CSS:

div[data-placeholder]:not(:focus):not([data-div-placeholder-content]):before {
    content: attr(data-placeholder);
    float: left;
    margin-left: 2px;
    color: #b3b3b3;
}

There’s a lot here, so let’s break that down:

  • div[data-placeholder] The placeholder should only be shown on divs which have a data-placeholder attribute.
  • :not(:focus) Hide the placeholder when the div is focused.
  • :not([data-div-placeholder-content]) Hide the placeholder when the div has a data-div-placeholder-content attribute. See JavaScript below. This is my workaround for the problem that :contains doesn’t exist.
  • :before This is a CSS pseudo element. The user will see the text, but it won’t be in the (standard) DOM.
  • content: attr(data-placeholder); The content of the pseudo element should be the text contained in the data-placeholder attribute.
  • The rest is just cosmetics.

In order to hide the "placeholder" text when the div contains user-supplied text, I had to resort to a bit of JavaScript/jQuery:

(function ($) {
	$(document).on('change keydown keypress input', 'div[data-placeholder]', function() {
		if (this.textContent) {
			this.dataset.divPlaceholderContent = 'true';
		}
		else {
			delete(this.dataset.divPlaceholderContent);
		}
	});
})(jQuery);

This code runs whenever any div containing a data-placeholder attribute on the document receives a keydown or keypress event. It then sets the data-div-placeholder-content attribute of that div if it contains any text and clears it if not. Hopefully, that much is obvious. But what’s up with the change and input events? Do those even exist?

Well, sort of. divs don’t normally fire the change event when the user types. However, I needed a way to set the attribute state correctly if I ever change the div text in JavaScript code. Since there is no obvious event to use, I just send change. So code like this:

$("#myDiv").text("foo");

becomes:

$("#myDiv").text("foo").trigger("change");

Without explicitly triggering change, both the "foo" and the placeholder text would be shown, which is obviously undesirable.

input is a relatively new event and is not supported by all popular browsers at this time. It’s a better choice than keydown and keypress, but I include those two, as well as input, in an attempt to support as many browsers as possible.

I’ve published the source code as a plugin on GitHub. It’s arguably overkill to turn this into a plugin, but you don’t have to use it that way; you can just include the CSS and JavaScript inline with the rest of your code.

{ 5 } Comments

  1. Joko Rivai | February 3, 2013 at 6:52 am | Permalink

    Is it necessary to include focus/blur event within $(codument).on(…)? Or does the has no focus?

  2. David Moorhouse | February 4, 2013 at 9:58 pm | Permalink

    Clever, but ultimately it just makes your code that much harder to maintain :(

  3. Fede Torre | May 22, 2013 at 7:09 pm | Permalink

    Great tutorial. Thanks!! I modified it to work with td’s in a table instead of div’s and it works nicely.

  4. vzhen | August 1, 2013 at 6:21 am | Permalink

    Thank you for this awesome plugin.

    I got some problem. I modified this to angularjs version.
    So far so good.

    But angularjs Jqlite doesn’t have trigger() and it only has triggerHandler() which only affected first matched element.

    I have multiple contentedtable in 1 page.

    Any idea?

    Note: I don’t want to use angular with jquery

  5. gremwell | October 9, 2013 at 5:35 pm | Permalink

    What about using the :empty css selector?

Post a Comment

Your email is never published nor shared. Required fields are marked *

Bad Behavior has blocked 713 access attempts in the last 7 days.

Close