So I figured out a way to do this (hence adding it as an answer), but it feels very very ugly, and I'm sure I'm not using some basic Aura abilities.. so any improvements are more than welcome.
Here's the code:
Component markup:
<aura:component implements="forceCommunity:availableForAllPageTypes" access="global">
<div class="slds-tabs--default">
<ul class="slds-tabs--default__nav" role="tablist">
<li aura:id="tabpage" class="slds-tabs--default__item slds-text-heading--label slds-active" title="Active" onclick="{!c.changeTab}"
role="presentation"><a class="slds-tabs--default__link" href="javascript:void(0);" role="tab" tabindex="0" aria-selected="true" aria-controls="tab-default-1" id="tab-default-1__item">Active</a></li>
<li aura:id="tabpage" class="slds-tabs--default__item slds-text-heading--label" title="Previous" onclick="{!c.changeTab}"
role="presentation"><a class="slds-tabs--default__link" href="javascript:void(0);" role="tab" tabindex="-1" aria-selected="false" aria-controls="tab-default-2" id="tab-default-2__item">Previous</a></li>
</ul>
<div aura:id="tab-default-1" class="slds-tabs--default__content slds-show" role="tabpanel" aria-labelledby="tab-default-1__item">Item One Content</div>
<div aura:id="tab-default-2" class="slds-tabs--default__content slds-hide" role="tabpanel" aria-labelledby="tab-default-2__item">Item Two Content</div>
</div>
</aura:component>
Helper (used to find the div I clicked in):
({
getContainerDiv: function(event, element) {
var elem;
if (!element) {
elem = event.srcElement;
}
else {
elem = element;
}
if (elem.classList.contains('slds-tabs--default__item')) {
return elem;
}
else {
return this.getContainerDiv(event, elem.parentElement);
}
}
})
JS Controller (main logic):
({
changeTab: function(component, event, helper) {
var clickedTab = helper.getContainerDiv(event, null);
var tabs = component.find('tabpage');
for(idx = 0; idx < tabs.length; idx++)
{
tab = tabs[idx].elements[0];
$A.util.removeClass(tab, 'slds-active');
$A.util.removeClass(component.find(tab.children[0].getAttribute('aria-controls')), 'slds-show');
$A.util.addClass(component.find(tab.children[0].getAttribute('aria-controls')), 'slds-hide');
}
$A.util.addClass(clickedTab, 'slds-active');
$A.util.addClass(component.find(clickedTab.children[0].getAttribute('aria-controls')), 'slds-show');
$A.util.removeClass(component.find(clickedTab.children[0].getAttribute('aria-controls')), 'slds-hide');
}
})
If you want to do this without the need for a button you need your component to handle the init event:
<aura:component>
<aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
</aura:component>
Sample Controller:
({
doInit: function(cmp) {
window.open("http://www.google.com");
}
})
Best Answer
You can't do this with lightning:tab, because there is no attribute that supports this, and even the Lightning Design System has no provision for disabled tabs. Instead, you might want to dynamically create the tabs as the user progresses, so they have the ability to go back and forth as they continue filling out the data.