The dashboard is designed to be highly customizable. You can modify the UI, add new widgets, adjust styling, and extend functionality to meet your specific needs.
We encourage you to fork the repository and customize the dashboard to your branding and requirements.
Styling & Theming
The dashboard uses Tailwind CSS with custom theme configuration and CSS variables for consistent styling.
Theme Configuration
Customize colors, typography, and spacing in tailwind.config.js:
const { fontFamily } = require ( 'tailwindcss/defaultTheme' )
const { colors , typography } = require ( './styles/theme' )
/** @type {import('tailwindcss').Config} */
module . exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx}' ,
'./components/**/*.{js,ts,jsx,tsx}' ,
],
theme: {
fontFamily: {
sans: [ typography . fontFamily , ... fontFamily . sans ],
},
extend: {
colors ,
textColor: {
base: '#25283D' ,
},
fontSize: {
md: '1rem' ,
},
},
},
plugins: [],
}
CSS Variables
The dashboard uses CSS variables for theming. Customize them in styles/globals.css:
styles/globals.css (example)
:root {
/* Colors */
--primary-color : #2D27F7 ;
--alternative-color : #8B5CF6 ;
--background-01-color : #FFFFFF ;
--text-blue-color : #2D27F7 ;
--border-01-color : #E5E7EB ;
/* Typography */
--font-family-sans : 'Inter' , system-ui , sans-serif ;
--font-family-iawriter : 'iA Writer Mono' , monospace ;
}
/* Dark mode support */
@media (prefers-color-scheme: dark) {
:root {
--background-01-color : #1F2937 ;
--text-color : #F9FAFB ;
--border-01-color : #374151 ;
}
}
Modify these CSS variables to instantly change the look and feel of the entire dashboard without touching component code.
Custom Fonts
The dashboard uses Inter for sans-serif text and iA Writer Mono for monospace text.
To change fonts, update app/layout.tsx:
import { Inter } from 'next/font/google'
import localFont from 'next/font/local'
const sans = Inter ({
variable: '--font-family-sans' ,
subsets: [ 'latin' ],
fallback: [ 'system-ui' ],
})
const mono = localFont ({
variable: '--font-family-iawriter' ,
src: '../assets/fonts/iawritermonos-regular.woff2' ,
display: 'swap' ,
})
export default function RootLayout ({
children ,
} : {
children : React . ReactNode
}) {
return (
< html lang = "en" className = { ` ${ sans . variable } ${ mono . variable } ` } >
< body >{ children } </ body >
</ html >
)
}
The dashboard includes several pre-built widgets that you can customize or extend.
Widgets are located in app/widgets/:
visitors.tsx - Real-time and historical visitor counts
pageviews.tsx - Page view trends
top-pages.tsx - Most visited pages
top-locations.tsx - Geographic distribution
top-sources.tsx - Traffic sources and referrers
top-devices.tsx - Device type breakdown
top-browsers.tsx - Browser statistics
Edit app/widgets.tsx to change the widget grid:
export const Widgets = () => {
return (
< div className = "grid grid-cols-2 gap-4" >
< Visitors />
< Pageviews />
< TopPages />
< TopLocations />
< TopSources />
< TopDevices />
< TopBrowsers />
</ div >
)
}
Change grid-cols-2 to grid-cols-3 for a three-column layout, or use responsive classes like grid-cols-1 md:grid-cols-2 lg:grid-cols-3.
Create a new widget file in app/widgets/:
app/widgets/custom-metric.tsx
import { Card } from '@/components/ui/Card'
import { SqlChart } from '@/components/ui/SqlChart'
import { useEndpoint } from '@/lib/hooks/use-endpoint'
type CustomMetric = {
date : string
value : number
}
export const CustomMetric = () => {
const { data , error , isLoading } = useEndpoint < CustomMetric []>(
'your_custom_pipe'
)
return (
< Card >
< SqlChart
title = "Custom Metric"
data = { data }
error = {error?. message }
isLoading = { isLoading }
xAxisKey = "date"
yAxisKey = "value"
color = "#2D27F7"
/>
</ Card >
)
}
Then add it to the widgets grid:
import { CustomMetric } from './widgets/custom-metric'
export const Widgets = () => {
return (
< div className = "grid grid-cols-2 gap-4" >
< Visitors />
< Pageviews />
< CustomMetric /> { /* Your new widget */ }
< TopPages />
{ /* ... other widgets */ }
</ div >
)
}
Adding New Pipes
To display data from a new Tinybird pipe:
Create Pipe in Tinybird
Create and publish your pipe in the Tinybird Console or CLI.
Add to JWT Scopes
Add the pipe name to the resources array in components/AuthDialog.tsx: const resources = [
'domains' ,
'top_sources' ,
// ... other pipes
'your_new_pipe' , // Add here
]
Create Widget Component
Use the useEndpoint hook to fetch data from your pipe: const { data , error , isLoading } = useEndpoint < YourDataType []>(
'your_new_pipe'
)
Display Data
Use the SqlChart, PipeTable, or custom components to visualize the data.
Modify the header in components/Header.tsx:
components/Header.tsx (excerpt)
export const Header = ({ onAskAiClick , onLogout } : HeaderProps ) => {
return (
< nav className = "h-16 sticky top-0 z-20 py-2.5 px-5 bg-[var(--background-01-color)] border-b border-[var(--border-01-color)]" >
< a
href = "https://tinybird.co"
target = "_blank"
rel = "noopener noreferrer"
className = "inline-flex items-center gap-2.5"
>
< div className = "aspect-square w-6 h-6 bg-[var(--primary-color)] rounded flex items-center justify-center" >
< img src = "/icon.svg" alt = "" width = { 16 } height = { 16 } />
</ div >
< Text variant = "button" color = "default" >
Your Company Name
</ Text >
</ a >
{ /* ... rest of header */ }
</ nav >
)
}
Custom Logo
Replace the logo by updating the image source:
Add your logo to public/your-logo.svg
Update the <img> tag in Header.tsx:
< img src = "/your-logo.svg" alt = "Your Company" width = { 32 } height = { 32 } />
Customizing Time Ranges
Modify available time ranges in lib/hooks/use-time-range.ts:
lib/hooks/use-time-range.ts (example)
export function useTimeRange () {
const options = [
{ value: 'today' , label: 'Today' },
{ value: 'yesterday' , label: 'Yesterday' },
{ value: '7d' , label: 'Last 7 days' },
{ value: '30d' , label: 'Last 30 days' },
{ value: '90d' , label: 'Last 3 months' },
{ value: '12m' , label: 'Last 12 months' },
// Add custom ranges here
]
const [ value , setValue ] = useState ( options [ 2 ]. value ) // Default to 7 days
return { value , setValue , options }
}
AI Chat Customization
The AI chat feature can be customized or embedded in other applications.
Set the AI endpoint in your environment variables:
NEXT_PUBLIC_ASK_TINYBIRD_ENDPOINT = https://your-custom-ai-endpoint.com/api/chat
Embedding AI Chat
The AI chat components are modular and can be embedded anywhere:
import { AIChatStandalone } from '@/components/ai-chat'
function CustomPage () {
return (
< div >
< h1 > Custom Analytics Page </ h1 >
< AIChatStandalone
placeholder = "Ask about your data..."
className = "max-w-4xl mx-auto p-6"
maxSteps = { 30 }
/>
</ div >
)
}
See components/ai-chat/README.md for detailed documentation on embedding options.
Dashboard Tabs
Customize or add tabs in app/DashboardTabs.tsx:
import { Tabs , TabsList , TabsTrigger , TabsContent } from '@/components/ui/Tabs'
import { Widgets , CoreVitals } from './widgets'
const DashboardTabs = () => {
return (
< Tabs defaultValue = "analytics" >
< TabsList className = "mb-4" >
< TabsTrigger value = "analytics" > Analytics </ TabsTrigger >
< TabsTrigger value = "speed" > Speed Insights </ TabsTrigger >
< TabsTrigger value = "custom" > Custom Tab </ TabsTrigger >
</ TabsList >
< TabsContent value = "analytics" >
< Widgets />
</ TabsContent >
< TabsContent value = "speed" >
< CoreVitals />
</ TabsContent >
< TabsContent value = "custom" >
{ /* Your custom content */ }
</ TabsContent >
</ Tabs >
)
}
Multi-Domain Support
The dashboard includes domain filtering. Customize the domain selector in components/ui/DomainSelect.tsx or modify the filtering logic in your Tinybird pipes.
Environment-Specific Configuration
Use different configurations for development and production:
const config = {
dashboardURL: process . env . NEXT_PUBLIC_TINYBIRD_DASHBOARD_URL as string ,
trackerToken: process . env . NEXT_PUBLIC_TINYBIRD_TRACKER_TOKEN as string ,
tenantId: process . env . TENANT_ID as string ,
isDevelopment: process . env . NODE_ENV === 'development' ,
isProduction: process . env . NODE_ENV === 'production' ,
} as const
export default config
Then use conditionally in your components:
import config from '@/lib/config'
if ( config . isDevelopment ) {
// Development-specific code
}
UI Component Customization
The dashboard uses custom UI components in components/ui/. You can modify these to change the look and behavior:
Button.tsx - Button styles and variants
Card.tsx - Card container styling
Chart.tsx / SqlChart.tsx - Chart configurations
Dialog.tsx - Modal dialogs
Input.tsx - Form inputs
Table.tsx / PipeTable.tsx - Data tables
Tabs.tsx - Tab navigation
Text.tsx - Typography components
import styles from './Button.module.css'
export const Button = ({ variant = 'solid' , size = 'medium' , ... props }) => {
return (
< button
className = { ` ${ styles . button } ${ styles [ variant ] } ${ styles [ size ] } ` }
{ ... props }
/>
)
}
components/ui/Button.module.css
.button {
border-radius : 8 px ;
font-weight : 600 ;
transition : all 0.2 s ;
}
.solid {
background : var ( --primary-color );
color : white ;
}
.solid:hover {
opacity : 0.9 ;
}
.medium {
padding : 8 px 16 px ;
font-size : 14 px ;
}
.large {
padding : 12 px 24 px ;
font-size : 16 px ;
}
Caching with SWR
Customize data fetching behavior in lib/hooks/use-endpoint.ts:
lib/hooks/use-endpoint.ts (example)
import useSWR from 'swr'
export function useEndpoint < T >( endpoint : string ) {
return useSWR < T >(
`/api/endpoints/ ${ endpoint } ` ,
fetcher ,
{
refreshInterval: 30000 , // Refresh every 30 seconds
revalidateOnFocus: false ,
dedupingInterval: 5000 ,
}
)
}
Lazy Loading
Improve initial load time by lazy loading widgets:
import { lazy , Suspense } from 'react'
import { Loader } from '@/components/ui/Loader'
const Visitors = lazy (() => import ( './widgets/visitors' ))
const Pageviews = lazy (() => import ( './widgets/pageviews' ))
export const Widgets = () => {
return (
< Suspense fallback = {<Loader />} >
< div className = "grid grid-cols-2 gap-4" >
< Visitors />
< Pageviews />
{ /* ... other widgets */ }
</ div >
</ Suspense >
)
}
Advanced Customization
Custom Middleware
Extend the authentication middleware in middleware.ts to add custom logic:
// Add IP allowlisting
const allowedIPs = [ '192.168.1.1' , '10.0.0.1' ]
const clientIP = request . ip || request . headers . get ( 'x-forwarded-for' )
if ( allowedIPs . includes ( clientIP )) {
return NextResponse . next ()
}
Custom API Routes
Add new API endpoints in app/api/:
import { NextRequest , NextResponse } from 'next/server'
import { getTinybirdClient } from '@/lib/server'
export async function GET ( request : NextRequest ) {
const client = getTinybirdClient ()
const data = await client . query ( 'your_pipe' )
return NextResponse . json ( data )
}
Building for Your Brand
Update Branding
Replace logos, colors, and typography to match your brand identity
Customize Widgets
Add, remove, or modify widgets to show metrics relevant to your business
Configure Authentication
Set up authentication that integrates with your existing identity provider
Add Custom Features
Extend the dashboard with features specific to your use case
Deploy and Share
Deploy your customized dashboard and share it with your team
Example Customizations
Add revenue and conversion widgets
Track product views and purchases
Show shopping cart abandonment rates
Display customer lifetime value
Track user signups and activations
Monitor feature usage
Show subscription metrics
Display churn and retention rates
Track article engagement
Monitor reading time and scroll depth
Show popular content categories
Display social sharing metrics
Track campaign performance
Monitor ad spend and ROI
Show conversion funnels
Display attribution metrics
Resources
Next.js Documentation Learn more about Next.js features and best practices
Tailwind CSS Explore Tailwind utility classes and customization
Tinybird SDK API reference for the Tinybird JavaScript SDK
Fork on GitHub Fork the repository and start customizing
Next Steps
Deploy Your Dashboard Deploy your customized dashboard to production
Configure Tinybird Learn how to create custom pipes and optimize queries