Adding a Mobile‑First TabBar to the Albaida‑Tickets Dashboard (React + Next.js)
TL;DR: I added a client‑side TabBar.tsx component to make the Albaida‑Tickets admin UI usable on phones, wiring it to Next.js routing and styling it with Tailwind. The change lives in components/TabBar.tsx and is referenced from pages/dashboard.tsx, fixing the “content overflows on mobile” symptom.
The Problem
The Albaida‑Tickets dashboard was built as a desktop‑only grid of cards. When I opened the page on an iPhone X, the layout overflowed horizontally, the filter sidebar never collapsed, and the bottom navigation was missing. The console showed no errors, but the UI was unusable:
Viewport width: 375px
Dashboard container width: 1024px → horizontal scroll
Because the app is a Next.js project with server‑rendered pages, I couldn’t just drop a CSS media query on the existing layout; the navigation needed a mobile‑specific component that works on the client (uses next/link and useRouter).
What I Tried First
My first attempt was to hide the existing sidebar with a simple Tailwind utility (hidden md:block) and rely on the top navigation for navigation. That approach failed for two reasons:
-
No persistent navigation: Users lost quick access to the main sections (
Tickets,Analytics,Settings). - Route detection broke: The active state was tied to the server‑side layout, so the highlighted link never updated after a client‑side navigation.
I also tried injecting a pure CSS “bottom bar” via a global stylesheet, but because the component needed to read the current route to apply the active style, the static CSS couldn’t react to client navigation.
The Implementation
1. Create the TabBar component
File: components/TabBar.tsx
import Link from 'next/link';
import { useRouter } from 'next/router';
import clsx from 'clsx';
type Tab = {
href: string;
label: string;
icon: React.ReactNode;
};
const tabs: Tab[] = [
{
href: '/dashboard',
label: 'Tickets',
icon: (
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path d="M3 7h18M3 12h18M3 17h18" />
</svg>
),
},
{
href: '/analytics',
label: 'Analytics',
icon: (
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path d="M12 20V10M18 20V4M6 20v-8" />
</svg>
),
},
{
href: '/settings',
label: 'Settings',
icon: (
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path d="M12 15a3 3 0 100-6 3 3 0 000 6z" />
<path d="M19.4 15a1.65 1.65 0 01.33 1.82l-1 1.73a1.65 1.65 0 01-1.9.81l-2-.5a1.65 1.65 0 01-1.1-.9l-.6-1.1a7.07 7.07 0 01-2.4 0l-.6 1.1a1.65 1.65 0 01-1.1.9l-2 .5a1.65 1.65 0 01-1.9-.81l-1-1.73a1.65 1.65 0 01.33-1.82l.9-.7a7.07 7.07 0 010-2.4l-.9-.7a1.65 1.65 0 01-.33-1.82l1-1.73a1.65 1.65 0 011.9-.81l2 .5a1.65 1.65 0 011.1.9l.6 1.1a7.07 7.07 0 012.4 0l.6-1.1a1.65 1.65 0 011.1-.9l2-.5a1.65 1.65 0 011.9.81l1 1.73a1.65 1.65 0 01-.33 1.82l-.9.7a7.07 7.07 0 010 2.4l.9.7z" />
</svg>
),
},
];
export default function TabBar() {
const { pathname } = useRouter();
return (
<nav className="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 md:hidden">
<ul className="flex justify-around">
{tabs.map(({ href, label, icon }) => {
const isActive = pathname === href;
return (
<li key={href}>
<Link href={href}>
<a
className={clsx(
'flex flex-col items-center py-2 text-sm',
isActive
? 'text-blue-600'
: 'text-gray-600 hover:text-gray-800'
)}
>
{icon}
<span>{label}</span>
</a>
</Link>
</li>
);
})}
</ul>
</nav>
);
}
Key points
-
Client‑side routing:
useRoutergives us the current path, enabling the active style without a full page reload. -
Tailwind utilities: The component is hidden on
mdand larger (md:hidden) so it only appears on phones. -
clsx: Keeps conditional class logic readable.
2. Wire the component into the dashboard layout
File: pages/dashboard.tsx (excerpt)
import DashboardGrid from '@/components/DashboardGrid';
import TabBar from '@/components/TabBar';
export default function DashboardPage() {
return (
<>
<main className="pt-4 pb-20 md:pb-4">
<DashboardGrid />
</main>
{/* Mobile navigation */}
<TabBar />
</>
);
}
I placed <TabBar /> after the main content so it stays fixed at the bottom. The pb-20 on the <main> element reserves space for the bar on mobile.
3. Update the changelog and metadata
The weekly automation script adds a new entry to content/2026/10/24/albaida-tickets/changelog.md:
## [2026-10-24] albaida-tickets
### Added
- `components/TabBar.tsx` – new bottom navigation for mobile devices (client component with Link, route detection and Tailwind styling)
And flips the generation flags in content/2026/10/24/albaida-tickets/metadata.json:
- "medium_generated": false,
- "substack_generated": false,
+ "medium_generated": true,
+ "substack_generated": true,
These changes are consumed by the content‑automation pipeline that builds the weekly newsletter and the Dev.to post you’re reading now.
4. Verify the fix
Running the dev server (npm run dev) and opening the page on a Chrome device emulator showed:
- No horizontal scroll.
- The TabBar appears at the bottom, icons highlight correctly when navigating.
- The desktop layout remains untouched (
mdbreakpoint).
I also added a quick Cypress test (`c
Part of my Build in Public series — sharing the real process of building SaaS projects from Playa del Carmen, México.
Repo: zaerohell/content-automation · 2026-10-25
#playadev #buildinpublic
Top comments (0)