Tutorials written for jQuery UI 1.8 call add, remove, select and rotate methods. The 1.9 upgrade guide deprecated add, remove and select and removed rotate outright, and the 1.10 guide removed add, remove and select, so that code fails on any current release.

The widget itself is still the easiest route for sites that already load jQuery UI, since panels swap without a page reload. A list of anchors, matching panel elements and one .tabs() call produce the strip, and ARIA roles and keyboard handling arrive automatically.

What Are jQuery Tabs?

Tabs hide every content panel except one and put a row of clickable headers above them. Click a header and its panel takes over. In jQuery projects the word gets stretched across several different things, and tutorials mix them freely, so it pays to know which one a snippet targets before you start debugging.

The official version is the jQuery UI Tabs widget, added in jQuery UI 1.0 and started with a single .tabs() call. Plenty of sites skip it and write a few click handlers that hide every panel and show the chosen one. Then there are small plugins such as jQuery-Tabs by jralph and jQuery EasyTabs, which avoid jQuery UI altogether.

All of them rest on the same idea. Some plain JavaScript event handling toggles which panel is visible while the tab list stays in the DOM.

Where they split is markup and styling. jQuery UI generates its own classes and ARIA attributes. EasyTabs describes itself as tabs without hidden styling, so the CSS is entirely yours.

I use the jQuery UI widget as the reference for the rest of this article, since it ships with documented options, events and keyboard behavior. Products such as jQWidgets jqxTabs and W2UI tabs are separate widget sets with their own APIs, so their settings do not carry over.

jQuery still runs on 65.3% of all websites (W3Techs, October 2026), so tab code written for it stays in circulation.

jQuery UI is another story. It has been in maintenance mode since 2021, according to the jQuery UI blog, which means security fixes and compatibility updates. New features are not coming.

How to Create jQuery Tabs with jQuery UI

Setup from an empty page goes like this.

  1. Load jQuery, jQuery UI and a theme stylesheet. The official API example pulls jQuery 3.7.1 and jQuery UI 1.13.3 from code.jquery.com, and npm install jquery-ui@1.14.2 installs the 1.14.2 release.
  2. Write the tab list as a ul or ol, with each li holding an anchor that has a hash href.
  3. Add one panel element per tab, with an id equal to that anchor’s hash.
  4. Call .tabs() on the outer container once the DOM is ready.
  5. Reload and confirm the first panel is open, since active defaults to 0.

The markup itself is plain HTML markup, nothing exotic. Tab titles go in list items as anchors, and a panel can be any valid element.

What is shaping UX design today?

Uncover the newest UX design statistics: user behavior, design trends, ROI data, and insights driving better digital experiences.

Check Them Out →
<div id="tabs">
<ul>
<li><a href="#fragment-1">One</a></li>
<li><a href="#fragment-2">Two</a></li>
</ul>
<div id="fragment-1"><p>First panel.</p></div>
<div id="fragment-2"><p>Second panel.</p></div>
</div>

<script>
$(function () {
$("#tabs").tabs();
});
</script>

The official docs say outright that the widget needs its functional CSS or it will not work. So the theme stylesheet isn’t optional decoration.

By default the widget converts the first descendant list inside the container. Put the tab list before any other list, or override the _getList extension point.

jQuery UI 1.14.2 was tested against jQuery 1.12.4, 2.2.4, 3.6.4, 3.7.1 and 4.0.0 (jQuery UI 1.14.2 release post, 2026). jQuery 4.0.0 shipped on 17 January 2026 and dropped support for IE 10 and older (jQuery team).

Options That Control How jQuery Tabs Behave

Every option can be set at initialization or changed later with .tabs("option", name, value). Defaults and accepted values are in the table.

OptionDefaultAccepted valuesEffect
active0integer, falseWhich panel is open
classesui-corner classesobjectExtra classes per widget element
collapsiblefalsebooleanLets the open panel close
disabledfalseboolean, arrayDisables all tabs or listed indexes
event“click”stringEvent that activates a tab
heightStyle“content”auto, fill, contentHow panel height is set
hidenullboolean, number, string, objectAnimation when a panel closes
shownullboolean, number, string, objectAnimation when a panel opens

Choosing the starting tab

active takes a zero-based index, so active: 1 opens the second tab.

A positive integer counts from the front, a negative one counts back from the last panel, and false collapses everything. That last value only works when collapsible is true. Put the two together and you get a widget that starts closed.

Opening a tab from the URL hash

The widget takes an index, not a hash, so a deep link needs a few lines of your own code.

var hash = window.location.hash;
var index = $("#tabs ul a").index($('#tabs ul a[href="' + hash + '"]'));

$("#tabs").tabs({ active: index > -1 ? index : 0 });

The fallback to 0 matters. A value of -1 would select the last panel, because negative numbers count backward.

To keep the address bar in sync, call history.replaceState inside the activate callback with the new tab’s anchor href. That uses the History API and leaves the back button alone.

Remembering the last active tab

Write the new index to Web Storage inside the activate callback, then read it back and pass it to active at initialization.

var saved = parseInt(sessionStorage.getItem("activeTab"), 10);

$("#tabs").tabs({
active: isNaN(saved) ? 0 : saved,
activate: function (event, ui) {
if (ui.newTab.length) {
sessionStorage.setItem("activeTab", ui.newTab.index());
}
}
});

The length check covers collapsible tabs. When a panel collapses, ui.newTab is an empty jQuery object, and its index would come back as -1.

Older tutorials use the cookie option for this. It was removed in jQuery UI 1.10 (jQuery UI 1.10 upgrade guide), so the storage code has to be yours.

Switching on hover and animating the change

Set event: "mouseover" and tabs activate on hover. Touch screens have no hover, so click is the safer default for public pages, and I’d stick with it.

Animating the change goes through show and hide.

  • Both take a duration in milliseconds
  • A jQuery method name such as "slideDown" also works
  • A jQuery UI effect name such as "fold" needs the optional Effects Core
  • An object like { effect: "explode", duration: 1000 } sets effect, delay, duration and easing

Leave out the effect inside an object and the widget falls back to fadeOut for hide and fadeIn for show.

How to Load jQuery Tabs Content with Ajax

Set a tab anchor’s href to a URL instead of a hash, and the widget loads that URL into the panel when the tab is activated.

<li><a href="/panels/pricing.html">Pricing</a></li>

The widget sends the request through jQuery.ajax, so it behaves like any standard Ajax request. A URL on another origin needs CORS headers from the server.

beforeActivate fires first and can cancel the switch. beforeLoad comes next, just before the request, and exposes ui.tab, ui.panel, ui.jqXHR and ui.ajaxSettings. It can cancel the load too, though the panel still activates. Once the response fills the panel, load fires.

The ajaxSettings object is editable in beforeLoad, but jQuery has already applied prefilters, processed data and chosen the request type. The official API documentation puts beforeLoad under the same limits as the beforeSend callback.

Error handling is yours to write. The jqXHR object is a standard jQuery object, so .fail() catches HTTP errors.

$("#tabs").tabs({
beforeLoad: function (event, ui) {
ui.jqXHR.fail(function () {
ui.panel.html("This tab could not be loaded.");
});
}

});

Call .tabs("load", 1) to fetch a panel by index, or pass the href instead.

While a request runs, the tab’s list item carries the ui-tabs-loading class. That class is the hook for a CSS spinner, because the spinner option from the 1.8 era was removed in jQuery UI 1.10.

Events and Methods for Controlling jQuery Tabs from Code

The Tabs widget fires 5 events and exposes 8 methods (official API documentation, jQuery UI 1.14). That is enough to switch tabs from code and react whenever one changes.

create runs once when the widget is built, and ui.tab and ui.panel hold the active pair, or empty jQuery objects if the widget starts collapsed. beforeActivate runs before a switch and can cancel it with event.preventDefault(). Then there is activate, which runs after the switch and its animation finish. It does not fire for the initial tab, so use create for that.

Each event works as an option callback or as a bound event, for example $(".selector").on("tabsactivate", fn). The ui object carries newTab, oldTab, newPanel and oldPanel.

On the method side, start with option, which reads or sets any option, so .tabs("option", "active", 2) switches tabs from code. The rest are smaller.

  • disable and enable take an index or an href, and the selected tab cannot be disabled
  • load fetches a remote panel
  • refresh processes tabs added or removed in the DOM and recalculates panel height
  • instance returns the widget instance, or undefined if the element was never initialized
  • destroy restores the pre-init state, and widget returns the container

To disable several tabs at once, set the disabled option to an array such as [1, 2, 3].

Adding and removing tabs after jQuery UI 1.9

The 1.8 API had add and remove methods. The 1.9 upgrade guide deprecated both and pointed to refresh, and the 1.10 guide lists them as removed.

1.8 featureStatusUse instead
add and remove methodsRemoved in 1.10Edit the DOM, then call refresh
select methodRemoved in 1.10Set the active option
select eventRemoved in 1.10beforeActivate event
rotate methodRemoved in 1.9No built-in replacement

A March 2011 post on the jQuery UI blog laid out a plan to support the old APIs in 1.9 and drop them in 2.0. The 1.9 upgrade guide moved the removal up to 1.10, so tutorials written for 1.8 break on any current version.

Adding a tab now means inserting the list item and the panel yourself, then telling the widget.

var id = "fragment-4";

$("#tabs ul").append('<li><a href="#' + id + '">Four</a></li>');
$("#tabs").append('<div id="' + id + '"><p>Fourth panel.</p></div>');
$("#tabs").tabs("refresh");

Removal is the same in reverse. Delete the li and its panel, then call refresh.

Are jQuery Tabs Accessible?

The widget handles the basics. It sets the tab roles, tracks selection state and moves focus with the arrow keys, but panel focus and vertical orientation are left to you, and the arrow keys also block page scrolling.

According to the widget source (jquery-ui repository, main branch, October 2026), the list gets role="tablist", each li gets role="tab" and each panel gets role="tabpanel". Those ARIA attributes are linked through aria-controls and aria-labelledby.

On its own the widget maintains aria-selected and aria-expanded on tabs and aria-hidden on panels. Disabled tabs get aria-disabled. Remote panels get aria-live="polite", plus aria-busy="true" while a request runs, and a roving tabindex keeps only the active tab in the Tab order.

Against the W3C WAI-ARIA Authoring Practices Guide tabs pattern (2026), the keyboard model differs in a few spots.

BehaviorjQuery UI TabsAPG tabs pattern
Arrows, Home, EndMove focus, wrap, activate after 300 msMove focus, wrap, activation optional
Up and DownAlso move focus and block page scrollingUnused in horizontal lists
Space and EnterSpace activates, Enter activates or togglesEither key activates
Vertical listsNo aria-orientation setSet aria-orientation="vertical"
Panel in Tab orderNo tabindex on panelsAdd tabindex="0" if no focusable content

The 300 ms delay restarts on every arrow press, so a tab that focus only passes through never activates. Hold Ctrl or Cmd with an arrow key to move focus without activating at all.

The APG recommends automatic activation only when panels appear without noticeable latency, which usually means preloaded content. Ajax tabs break that condition, so the delay and the Ctrl route matter more there.

Add tabindex="0" to any panel without focusable content. The widget never sets it, and the Bootstrap 5.3 documentation gives the same advice for its own tabs.

How to Style jQuery Tabs

The widget’s structural CSS and a theme stylesheet do most of the visual work, and your own overrides, keyed to the ui-tabs classes, handle the rest.

The official API example loads the Smoothness theme, themes/smoothness/jquery-ui.css, from code.jquery.com. A custom build needs core.css, tabs.css and theme.css, the 3 files the widget source declares as its CSS dependencies.

  • ui-tabs marks the outer container, and ui-tabs-collapsible joins it when collapsible is on
  • The tab list carries ui-tabs-nav
  • Each list item gets ui-tabs-tab, with ui-tabs-active added to the open one
  • ui-tabs-anchor sits on the link inside each tab
  • Panels use ui-tabs-panel, and only the active one is visible

The theme’s selectors are specific, so load your stylesheet after it and anchor overrides to an id.

#tabs .ui-tabs-nav .ui-tabs-tab.ui-tabs-active {
background: #0b5fff;
border-color: #0b5fff;
}

#tabs .ui-tabs-nav .ui-tabs-tab.ui-tabs-active a {
color: #fff;
}

ThemeRoller designs and downloads custom themes, and its gallery holds 25 ready-made ones, from Base and Smoothness to Vader (jQuery UI ThemeRoller, 2026). To hang your own class on the container without touching markup, pass classes: { "ui-tabs": "my-tabs" }.

Vertical tabs

Add ui-tabs-vertical and ui-helper-clearfix through the classes option. The CSS then floats the list left, clears each tab, and lets the panel fill the space beside it.

.ui-tabs-vertical .ui-tabs-nav { float: left; width: 12em; }
.ui-tabs-vertical .ui-tabs-nav .ui-tabs-tab { clear: left; width: 100%; margin: 0 0 .2em; }
.ui-tabs-vertical .ui-tabs-panel { overflow: hidden; }

The widget does not set the orientation for ARIA, so you do. Up and Down already move focus, which means the key handling needs no change.

$("#tabs").tabs({
classes: { "ui-tabs": "ui-tabs-vertical ui-helper-clearfix" }
});

$("#tabs > ul").attr("aria-orientation", "vertical");

Responsive layout

The widget has no responsive mode. None of its 8 options changes the layout by viewport.

Panel height is the one value it computes. With heightStyle set to “auto” or “fill”, pixel heights are fixed at creation, so call .tabs("refresh") after a resize.

Below a breakpoint, a media query can stack the tab list into a column.

@media (max-width: 600px) {
#tabs .ui-tabs-nav .ui-tabs-tab { float: none; margin: 0; }
#tabs .ui-tabs-nav .ui-tabs-anchor { float: none; display: block; }
}

A tab row that fits at 1200 px rarely fits at 360 px, which is a responsive design problem the widget leaves to you. Plugins such as Responsive Tabs and Easy responsive tabs to accordion switch to an accordion at a CSS breakpoint instead. Both are listed on plugins.jquery.com with releases from 2013 and 2014, and the site now points visitors to npm, where Responsive Tabs has newer releases.

Why jQuery Tabs Stop Working

Most of what breaks here is documented somewhere, either in the widget source or in the upgrade guides and jQuery 4 release notes. The table maps each symptom to its fix.

CauseSymptomFix
Stylesheet missingWidget does not behave as tabsLoad the theme, or core.css and tabs.css
Anchor matches no panelMismatching fragment identifier errorMatch the hash to a panel id inside the container
Base element on the pageIn-page tab fires an Ajax requestRemove it, or write the full page URL plus hash
Code from the 1.8 eraRemoved methods error, removed options are ignoredSwitch to refresh and active
jQuery 4 upgradeTypeError from a removed jQuery functionReplace calls with native equivalents

The mismatching fragment error, jQuery UI Tabs: Mismatching fragment identifier., is thrown when neither the clicked tab’s panel nor the currently open panel can be found (widget source, October 2026). The widget looks for panels inside the container only, so a panel placed outside it counts as missing.

A base element on the page changes how #tab-1 resolves. The widget treats an anchor as an in-page tab only when its origin, pathname and query string match the current page, so the tab ends up handled as remote.

The sources also disagree about the active option. The API documentation lists a default of 0 and an index value, while the widget source defaults it to null and, at creation, opens the tab whose panel id matches location.hash.

Passing active yourself switches that detection off, and the source has no hashchange handler, so the check runs once. Set active only when there is no hash to honor.

var saved = parseInt(sessionStorage.getItem("activeTab"), 10);
var options = {};

if (!window.location.hash && !isNaN(saved)) {
options.active = saved;
}

$("#tabs").tabs(options);

An out-of-range active value, such as a stored index for a tab that no longer exists, falls back to the first tab at creation. With collapsible on, it falls back to a collapsed widget instead.

If the console reports that jQuery is not defined, the init script ran before jQuery loaded or jQuery never loaded. Load order is jQuery, then jQuery UI, then your code.

A wrong script or stylesheet path appears in Chrome DevTools as a failed to load resource message in the Console and a red row in the Network tab.

jQuery 4.0.0 removed jQuery.isArray, jQuery.parseJSON, jQuery.trim, jQuery.type, jQuery.now, jQuery.isNumeric, jQuery.isFunction and jQuery.isWindow, among others (jQuery team, 2026). Any plugin or init code that still calls them throws, so update jQuery UI together with jQuery.

jQuery Migrate restores removed core APIs and, in its development build, logs console warnings that name the calls. Load it after jQuery and pair Migrate 4.0.2 with jQuery 4.0.0, and a site running anything older should move to jQuery 3.x first (jQuery Migrate README, 2026).

Migrate patches jQuery core only, so removed Tabs methods stay removed.

Should You Use jQuery UI Tabs or Another Approach?

Use the jQuery UI Tabs widget when jQuery UI is already on the page. Loading jQuery, jQuery UI and a theme stylesheet for a single tab strip is hard to justify.

ApproachDependenciesStylingBest fit
jQuery UI Tabs widgetjQuery, jQuery UI, theme CSSTheme plus overridesAjax tabs, built-in ARIA and keyboard
jQuery-Tabs pluginjQuery onlyYour own CSSPlain show-and-hide tabs
jQuery EasyTabsjQuery, plus a hashchange or Address plugin for back-button supportYour own CSSBookmarkable, auto-cycling tabs
Bootstrap 5 tabsBootstrap JS, no jQueryBootstrap CSSSites already on Bootstrap
Vanilla JavaScript or CSS-onlyNoneYour own CSSPages without jQuery

The jQuery UI widget brings Ajax, ARIA and keyboard behavior with it, at the price of 3 assets to load. jQuery-Tabs is the thinnest jQuery route, though its README leaves Ajax loading and sorting to your own jQuery. EasyTabs adds bookmarkable tabs and interval cycling, and no styling at all, so every rule is yours.

Bootstrap 5 tabs need no jQuery. The roles, ARIA attributes and panel tabindex are yours to write, though, and dropdowns inside tabs are unsupported (Bootstrap 5.3 docs). Going vanilla JavaScript or CSS-only drops dependencies entirely and hands you every keyboard and ARIA behavior to build.

On a Bootstrap 5 site, the framework’s own tab component is the natural pick. Its documented API is plain JavaScript, for example new bootstrap.Tab(el).

For a static page with 3 panels, CSS-only tabs need no script. They give up the widget’s keyboard handling.

GitHub Engineering’s 2018 write-up on removing jQuery from github.com listed a 30 kB dependency among its reasons. The team also built features as plain HTML first, with JavaScript added as progressive enhancement.

jQuery Tabs Examples To Check Out

Add File Tab Bar Animation

See the Pen
Add file tab bar animation
by Aaron Iker (@aaroniker)
on CodePen.

Tabs Modal In jQuery

Snapchat Tabs Switching

Fluid Tab Active State

Elastic Tabs

 

Flying Cards Tabs

Tab Bar Interaction with Dark Mode

3D Tab Bar

Mobile Navigation Concept

SVG Tabs

WeChat Tab Bar Redesign with jQuery

Tab Bar

Login Bar

Toggle Tabs

Gooey Navigation with CSS/SVG Filters

Horizontal Tab Menu Slider V0.2

Navigation Bar

Interactive Cart Icon

Setting Tabs

Tab Bar Menu Animation

App Navigation With jQuery And CSS Animation

Tab Bar Interaction

Simple Scrolling Nav Tabs

Light Tabs

Adaptive Tabs

Featured Tabs

jQuery Tabs FAQ

Should I use tabs or an accordion?

Tabs work for 3 to 6 parallel sections that visitors compare on a wide screen. Longer lists and narrow screens are accordion territory.

Both jQuery UI widgets keep one open panel at a time, through a single active value in their API documentation (2026).

Can jQuery tabs rotate automatically?

Not out of the box, but a timer does the job. Run a setInterval timer that raises the active option by one and wraps to 0 after the last tab, and pause it while a tab has focus or hover.

jQuery EasyTabs includes interval cycling if you want it built in.

Is content inside jQuery tabs indexed by Google?

Yes for in-page panels, because they sit in the HTML at load and Google indexes the rendered HTML (Google Search Central, 2026).

Ajax panels differ, since the widget fetches them when their tab activates. Keep anything you need indexed in in-page tabs.

Can you nest jQuery tabs or place them inside an accordion?

Yes. Call the widget on each container separately and keep every panel id unique, because the widget finds its panels by id inside its own container.

Accordion panels accept arbitrary markup per the jQuery UI API documentation, so a tab set fits inside one.

How do you use jQuery UI tabs in WordPress?

Enqueue the jquery-ui-tabs handle, which core registers with jquery-ui-core as its dependency, and that in turn depends on jQuery. WordPress loads jQuery in noConflict mode, so write jQuery(function ($) { ... }) instead of calling $ directly.

The widget still needs a theme stylesheet. To skip code entirely, adding tabs in WordPress with a block or plugin works too.

A Safe Upgrade Order for Legacy jQuery Tabs

Repair the jQuery UI Tabs calls first. Move jQuery to 3.x after that, and leave jQuery 4 for last. Removed Tabs methods throw errors that stop a script before jQuery Migrate can log anything useful, which is why the order matters.

  • Replace removed Tabs calls with refresh and the active option
  • Move jQuery to 3.x and clear every Migrate 3.x warning
  • Test jQuery 4.0.0 with Migrate 4.0.2, fixing each logged call

The price is 3 releases instead of 1, which buys a rollback point after each stage.

As of 15 October 2026 and the jQuery 4.0.0 notes, jQuery 5.0 is the announced next stage of IE support removal (jQuery team, 2026), and it would add a fourth round. Run each stage through cross-browser compatibility checks before it ships.

Bogdan Sandu
Latest posts by Bogdan Sandu (see all)