Everyone loves jQuery plugins. Copy a script tag, paste an example, boom, a carousel appears. It feels like cheat codes for the front end. I have been living in the trenches with jQuery since it edged past Prototype and MooTools in mindshare, and I get the rush. But there is a catch. Each plugin you pull in is a silent promise to your future self. You promise to debug it on IE6, to carry its weight on slow 3G, to bend your CSS around it, to keep it alive when the author gets bored and vanishes to Hacker News. That promise adds up. So today I want to talk about jQuery plugins in moderation. Why we reach for them, what they cost, and how to choose or write ones that do not bite you back.
The plugin reflex
jQuery 1.4 landed with nice speed bumps and better JSON handling, and the plugin scene exploded. For any widget you can name there is a zip file waiting: sliders, tabs, modals, validators, tooltips, date pickers. Using a jQuery plugin does three things that feel great in the moment.
- Speed to demo. A few lines and you have something that moves. Clients love moving things.
- Cross browser cover. A plugin author already fought with quirks in IE6 and friends. You get that battle for free.
- Shared patterns. Plugins create a shared way to do tabs or a carousel. That lowers the mental load for your team.
That is the reflex. Search, paste, tweak. The better reflex is to pause for a breath and ask what you are really getting. A plugin is not just features. It is weight, behavior, CSS opinions, and sometimes a second dependency buried inside. Some plugins ship a whole theme folder. Some rely on jQuery UI. Some want a bunch of markup you do not have. If you do not catch that up front, the quick win turns into a long Saturday.
There is also the matter of your stack. Are you loading jQuery from Google CDN or the Microsoft CDN yet? If not, you should at least consider a public CDN for core jQuery and then host your plugins locally. That gives you a better chance the user already has jQuery cached. Example with a simple local fallback:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="/js/jquery-1.4.2.min.js"><\/script>')</script>That one line check avoids a white screen if the CDN has a hiccup. Your plugin selection should work with this setup without drama.
The costs you do not see at first
Let us talk about the hidden bill. A plugin can cost you in four main ways.
- Weight. One plugin is fine. Three is still fine. Ten is not. People are loading pages on phones now. The iPhone and Android browser can handle a lot, but mobile radios are slow. Minify and pack, sure, but the cheapest byte is the one you never ship.
- Global mess. Some plugins drop globals or pollute markup with random classes and ids. That collides with your CSS or other plugins. Read the source once. If you see loose globals, walk away.
- Event chaos. jQuery makes binding events easy, which also makes leaks easy. On IE6 and IE7, leaked handlers hurt. Good plugins use namespaced events and offer a destroy method. If you do not see that, you are the destroy method.
- Accessibility and keyboard. Fancy UI without focus order is a trap. If a plugin traps focus or hides content from screen readers, you inherit that bug forever. Quick check: can you tab through it and use it without a mouse? If not, you have work to do.
A good safety move is to watch memory and event binding in dev. When a widget is removed, unbind by namespace and clear data. Here is a pattern your own plugin can follow:
(function($){
var defaults = {
activeClass: "is-active",
onChange: $.noop
};
$.fn.simpleTabs = function(options){
var settings = $.extend({}, defaults, options);
return this.each(function(){
var $root = $(this);
var $tabs = $root.find("[data-tab]");
var $panes = $root.find("[data-pane]");
function activate(name){
$tabs.removeClass(settings.activeClass)
.filter("[data-tab='"+name+"']").addClass(settings.activeClass);
$panes.hide()
.filter("[data-pane='"+name+"']").show();
settings.onChange.call($root[0], name);
}
// store api for later
$root.data("simpleTabs", { activate: activate });
// namespaced events for clean unbind
$root.on("click.simpleTabs", "[data-tab]", function(e){
e.preventDefault();
activate($(this).attr("data-tab"));
});
// init
activate($tabs.first().attr("data-tab"));
});
};
// a friendly destroy method
$.fn.simpleTabs.destroy = function($root){
$root.off(".simpleTabs").removeData("simpleTabs");
};
})(jQuery);Look for similar patterns in third party code. Events with a namespace. A clean destroy. Data stored with .data instead of random attributes. These are small signs that a plugin will age well.
Write fewer lines and smarter ones
The best plugin is the one you never need. jQuery itself gives you a lot. Two examples I see often:
- Smooth scroll links. You do not need a full scroll plugin for simple cases.
$("a[href^='#']").click(function(e){
var id = this.getAttribute("href");
var $target = $(id);
if ($target.length) {
e.preventDefault();
$("html, body").animate({ scrollTop: $target.offset().top }, 300);
}
});- Hide and show on click. Many modal or dropdown plugins are overkill if you just need a basic toggle.
$(".js-toggle").click(function(e){
e.preventDefault();
$($(this).attr("data-target")).toggle();
});If you do pick a plugin, wire it with delegation when you can. jQuery 1.4 makes this easy with .delegate. That plays nice with content added later and keeps the number of handlers low.
$("#list").delegate("li .remove", "click", function(e){
e.preventDefault();
$(this).closest("li").remove();
});On the loading side, group your plugins and defer the ones that render below the fold. If your page is script heavy, a loader like LABjs can help keep order without blocking the world.
<script src="/js/LAB.min.js"></script>
<script>
$LAB
.script("https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js").wait()
.script("/js/jquery.validate.min.js")
.script("/js/jquery.carousel.min.js")
.wait()
.script("/js/app.js");
</script>Note the waits. That keeps your app code from firing before jQuery or the plugins are ready. You do not need this for every site, but it is a nice trick for the fat pages that marketing loves to stack with widgets.
When plugins shine and when hand code wins
There is a sweet spot for plugins, and there are places where hand written jQuery just flies.
Reach for a plugin when
- You need a complex widget with many states. Think date pickers, sortable lists, or sliders with touch and mouse.
- You need cross browser details that are painful to repeat. Animations with fallbacks, drag and drop, or tricky positioning.
- You want a tried pattern where a community has already shared the edge cases and fixes.
- You can accept the CSS and markup the plugin expects without turning your codebase into a pretzel.
Write it yourself when
- The behavior is simple. Show, hide, validate two fields, scroll to, reorder a small list.
- You only need one feature from a huge plugin. Pulling 20 KB to use one method is not a good trade.
- You have tight performance needs. Every extra function call and event handler counts, especially on older phones and older IE.
- You care about full control over markup, classes, and events for testing and accessibility.
Practical checklist before you paste another script tag
- Does it solve your exact need without pulling in a kitchen sink of features you will never use
- Size on the wire minified and gzipped. Can you trim modules or build a custom bundle
- Last update and issue tracker. Is the author active. Are there open bugs about IE
- License. MIT or similar plays nice with client work. Be sure you can ship it
- Dependencies. Does it require jQuery UI or other scripts. Are versions pinned
- Events. Namespaced binds and a destroy method. No destroy means future cleanup pain
- Data and state. Uses
$.datarather than hidden globals or random attributes - Markup control. Can you feed your own HTML. Does it inject inline styles you cannot override
- CSS scope. Prefixed classes to avoid collisions. No global resets baked in
- Keyboard and ARIA. Tabbable, focus visible, ARIA roles where needed. Screen reader sanity check
- Performance. Uses delegation for lists. Avoids layout thrash in loops. No forced sync redraws
- Fallback. Degrades to plain HTML when JS is off. At least shows content, not an empty box
- Test quickly in IE6 or IE7 once. If it breaks at hello world, move on
- CDN strategy. Load jQuery from a CDN with a local fallback, keep plugins local for cache control
If you are building your own plugin and want a good starting point that hits most of the boxes above, save this snippet and iterate from there:
(function($){
var pluginName = "smartWidget";
var defaults = { speed: 200 };
function Plugin($el, options){
this.$el = $el;
this.options = $.extend({}, defaults, options);
this._init();
}
Plugin.prototype._init = function(){
var self = this;
self.$el.addClass("smart-widget");
self.$el.on("click."+pluginName, ".toggle", function(e){
e.preventDefault();
self.toggle();
});
};
Plugin.prototype.toggle = function(){
this.$el.find(".pane").stop(true, true).fadeToggle(this.options.speed);
this.$el.trigger(pluginName+":toggled");
};
Plugin.prototype.destroy = function(){
this.$el.off("."+pluginName).removeClass("smart-widget").removeData(pluginName);
};
$.fn[pluginName] = function(optionsOrMethod){
return this.each(function(){
var $this = $(this);
var inst = $this.data(pluginName);
if (!inst) {
$this.data(pluginName, new Plugin($this, optionsOrMethod));
} else if (typeof optionsOrMethod === "string" && inst[optionsOrMethod]) {
inst[optionsOrMethod].apply(inst, Array.prototype.slice.call(arguments, 1));
}
});
};
})(jQuery);This structure makes it easy to create, call methods, and destroy without leaving dirty footprints. It also plays nice with event namespacing and custom events for your app code.
One last note for right now. jQuery UI keeps maturing and is close to another point release. If your need fits one of its widgets, take that route instead of grabbing three separate plugins from random places. You get theme support, a known API, and a better chance it will still work when a new jQuery drops.
Moderation wins. Plugins are great. Piling plugins is not. Treat each script tag as a small contract with your future self. Make it clear, keep it short, and you will ship faster with less pain.
Fewer plugins. Better code. Happier nights.