Tabs
Navigation interface that organizes content into sections.
TailwindCSS
Vanilla JS
Account
Make changes to your account here. Click save when you're done.
Password
Change your password here. After saving, you'll be logged out.
package showcase
import "github.com/axzilla/templui/pkg/components"
templ Tabs() {
@components.Tabs(components.TabsProps{
Tabs: []components.Tab{
{
ID: "account",
Title: "Account",
Content: AccountTab(),
},
{
ID: "password",
Title: "Password",
Content: PasswordTab(),
},
},
TabsContainerClass: "w-full max-w-xs",
ContentContainerClass: "w-full max-w-xs",
})
}
templ AccountTab() {
@components.Card(components.CardProps{}) {
@components.CardHeader() {
@components.CardTitle() {
Account
}
@components.CardDescription() {
Make changes to your account here. Click save when you're done.
}
}
@components.CardContent() {
<div class="flex flex-col gap-4">
@components.Input(components.InputProps{
Type: components.InputTypeText,
Placeholder: "Name",
ID: "name",
Value: "John Doe",
// Label: "Name",
})
@components.Input(components.InputProps{
Type: components.InputTypeText,
Placeholder: "Username",
ID: "username",
Value: "@johndoe",
// Label: "Username",
})
</div>
}
@components.CardFooter() {
@components.Button(components.ButtonProps{Text: "Save changes"})
}
}
}
templ PasswordTab() {
@components.Card(components.CardProps{}) {
@components.CardHeader() {
@components.CardTitle() {
Password
}
@components.CardDescription() {
Change your password here. After saving, you'll be logged out.
}
}
@components.CardContent() {
<div class="flex flex-col gap-4">
@components.Input(components.InputProps{
Type: components.InputTypePassword,
Placeholder: "Current Password",
ID: "current_password",
// Label: "Current Password",
})
@components.Input(components.InputProps{
Type: components.InputTypePassword,
Placeholder: "New Password",
ID: "new_password",
// Label: "New Password",
})
</div>
}
@components.CardFooter() {
@components.Button(components.ButtonProps{Text: "Save password"})
}
}
}
package components
import "github.com/axzilla/templui/pkg/utils"
type Tab struct {
ID string
Title string
Content templ.Component
}
type TabsProps struct {
Tabs []Tab
TabsContainerClass string
ContentContainerClass string
}
templ TabsScript() {
<script nonce={ templ.GetNonce(ctx) }>
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.tabs-component').forEach(tabs => {
const tabID = tabs.dataset.tabs;
const buttons = tabs.querySelectorAll(`[data-tab-button="${tabID}"]`);
const contents = tabs.querySelectorAll(`[data-tab-content="${tabID}"]`);
const marker = tabs.querySelector(`[data-tab-marker="${tabID}"]`);
// Show first tab
buttons[0]?.classList.add('text-foreground', 'bg-background', 'shadow-sm');
contents[0]?.classList.remove('hidden');
// Set marker position
if (buttons[0] && marker) {
marker.style.width = buttons[0].offsetWidth + 'px';
marker.style.height = buttons[0].offsetHeight + 'px';
marker.style.left = buttons[0].offsetLeft + 'px';
}
// Click handler
buttons.forEach((btn, idx) => {
btn.onclick = () => {
buttons.forEach(b => b.classList.remove('text-foreground', 'bg-background', 'shadow-sm'));
btn.classList.add('text-foreground', 'bg-background', 'shadow-sm');
contents.forEach(c => c.classList.add('hidden'));
contents[idx].classList.remove('hidden');
marker.style.width = btn.offsetWidth + 'px';
marker.style.height = btn.offsetHeight + 'px';
marker.style.left = btn.offsetLeft + 'px';
};
});
});
});
</script>
}
templ Tabs(props TabsProps) {
{{ tabID := utils.RandomID() }}
<div class="tabs-component relative" data-tabs={ tabID }>
<div class={ "relative flex items-center justify-center h-10 p-1 rounded-lg select-none bg-muted text-muted-foreground", props.TabsContainerClass }>
for _, tab := range props.Tabs {
<button
type="button"
data-tab-button={ tabID }
class="relative z-20 flex-1 inline-flex items-center justify-center h-8 px-3 text-sm font-medium transition-all rounded-md cursor-pointer whitespace-nowrap hover:text-foreground"
>
{ tab.Title }
</button>
}
<div data-tab-marker={ tabID } class="absolute left-0 z-10 h-full duration-300 ease-out">
<div class="w-full h-full bg-background rounded-md shadow-sm"></div>
</div>
</div>
<div class={ "relative mt-2", props.ContentContainerClass }>
for _, tab := range props.Tabs {
<div
data-tab-content={ tabID }
class="relative hidden"
>
@tab.Content
</div>
}
</div>
</div>
}
Usage
1. Add the script to your page/layout:
// Option A: All components (recommended)
@utils.ComponentScripts()
// Option B: Just Tabs
@components.TabsScript()
2. Use the component:
@components.Tabs(components.TabsProps{...})