Nvim Statusline
A customizable statusline component for Neovim, inspired by the nvim-lualine plugin.
Component Source'use client'import { GitBranchIcon } from 'lucide-react'import { Button } from '@/components/ui/button'import { NvimStatusline, NvimStatuslineProvider, NvimStatuslineSectionA, NvimStatuslineSectionB, NvimStatuslineSectionC, NvimStatuslineSectionX, NvimStatuslineSectionY, NvimStatuslineSectionZ, useNvimStatusline,} from '@/components/ui/nvim-statusline'export default function NvimStatuslineDemo() { return ( <NvimStatuslineProvider> <ChangeModeButtons /> <div className='flex-1' /> <NvimStatuslineContent /> </NvimStatuslineProvider> )}function NvimStatuslineContent() { const { mode } = useNvimStatusline() return ( <NvimStatusline> <NvimStatuslineSectionA>{mode.toUpperCase()}</NvimStatuslineSectionA> <NvimStatuslineSectionB> <GitBranchIcon /> main </NvimStatuslineSectionB> <NvimStatuslineSectionC>~/app/page.tsx</NvimStatuslineSectionC> <NvimStatuslineSectionX>+15</NvimStatuslineSectionX> <NvimStatuslineSectionY>Top 1:1</NvimStatuslineSectionY> <NvimStatuslineSectionZ> {new Date().toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', })} </NvimStatuslineSectionZ> </NvimStatusline> )}function ChangeModeButtons() { const { modes, setMode } = useNvimStatusline() return ( <div className='container flex flex-wrap items-center gap-4 py-6'> {modes.map((mode) => ( <Button key={mode} variant='outline' style={{ color: `var(--${mode})` }} size='sm' onClick={() => setMode(mode)} > {mode.toUpperCase()} </Button> ))} </div> )}Installation
CLI
npx shadcn add https://yuki-ui.vercel.app/r/nvim-statusline.jsonnpx shadcn add https://yuki-ui.vercel.app/r/nvim-statusline.jsonpnpm dlx shadcn add https://yuki-ui.vercel.app/r/nvim-statusline.jsonbunx --bun shadcn add https://yuki-ui.vercel.app/r/nvim-statusline.jsonManual
Install the following dependencies:
npm install @bae-ui/reactAdd the following CSS variables to your globals.css file:
@theme inline {
...
--color-normal: var(--normal);
--color-visual: var(--visual);
--color-replace: var(--replace);
--color-insert: var(--insert);
--color-terminal: var(--terminal);
--color-command: var(--command);
}
:root {
...
--normal: oklch(0.533 0.188299 256.8803);
--visual: oklch(0.5945 0.1522 48.09);
--replace: oklch(0.5352 0.1882 2.43);
--insert: oklch(0.6273 0.17 149.2);
--terminal: oklch(0.4706 0.2205 304.22);
--command: oklch(0.6424 0.18 45.27);
}
.dark {
...
--normal: oklch(0.7178 0.1521 250.77);
--visual: oklch(0.8535 0.0907 84.06);
--replace: oklch(0.6931 0.1891 3.82);
--insert: oklch(0.6273 0.17 149.2);
--terminal: oklch(0.6986 0.1786 309.44);
--command: oklch(0.8124 0.1238 55.54);
}Copy and paste the following code into your project.
'use client'import { mergeProps } from '@base-ui/react'import { useRender } from '@base-ui/react/use-render'import * as React from 'react'import { cn } from '@/lib/utils'const MODES = [ 'normal', 'visual', 'replace', 'insert', 'terminal', 'command',] as consttype Mode = (typeof MODES)[number]const BG_COLORS = 'group-data-[mode=normal]/statusline:bg-normal group-data-[mode=visual]/statusline:bg-visual group-data-[mode=insert]/statusline:bg-insert group-data-[mode=replace]/statusline:bg-replace group-data-[mode=command]/statusline:bg-command group-data-[mode=terminal]/statusline:bg-terminal'const TEXT_COLORS = 'group-data-[mode=normal]/statusline:text-normal group-data-[mode=visual]/statusline:text-visual group-data-[mode=insert]/statusline:text-insert group-data-[mode=replace]/statusline:text-replace group-data-[mode=command]/statusline:text-command group-data-[mode=terminal]/statusline:text-terminal'const FILL_COLORS = 'group-data-[mode=normal]/statusline:fill-normal group-data-[mode=visual]/statusline:fill-visual group-data-[mode=insert]/statusline:fill-insert group-data-[mode=replace]/statusline:fill-replace group-data-[mode=command]/statusline:fill-command group-data-[mode=terminal]/statusline:fill-terminal'interface NvimStatuslineContextValue { mode: Mode modes: typeof MODES setMode: (value: React.SetStateAction<Mode>) => void}const NvimStatuslineContext = React.createContext<NvimStatuslineContextValue | null>(null)function useNvimStatusline() { const context = React.use(NvimStatuslineContext) if (context === null) throw new Error( 'useNvimStatusline must be used within a NvimStatuslineProvider', ) return context}function NvimStatuslineProvider({ children,}: Readonly<{ children: React.ReactNode }>) { const [mode, setMode] = React.useState<Mode>('normal') const value = React.useMemo(() => ({ mode, modes: MODES, setMode }), [mode]) return <NvimStatuslineContext value={value}>{children}</NvimStatuslineContext>}function NvimStatusline({ className, render, ...props}: useRender.ComponentProps<'footer'>) { const { mode } = useNvimStatusline() return useRender({ defaultTagName: 'footer', props: mergeProps<'footer'>( { className: cn( 'group/statusline sticky bottom-0 left-0 z-50 flex h-6 w-full items-center justify-between gap-0 bg-border px-4 font-mono transition-colors md:bottom-4', "[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className, ), }, props, ), render, state: { slot: 'nvim-statusline', mode, }, })}function NvimStatuslineSectionA({ className, children, ...props}: React.ComponentProps<'div'>) { return ( <div data-slot='nvim-statusline-section-a' className={cn('inline-flex h-6 items-center', className)} {...props} > <div className={cn( 'inline-flex h-6 items-center gap-2 pl-2 pr-1 text-background font-medium', BG_COLORS, )} > {children} </div> <NvimStatuslineSectionSeparator data-slot='nvim-statusline-section-separator-1' className={cn('size-6 rotate-90 bg-background', FILL_COLORS)} /> </div> )}function NvimStatuslineSectionB({ className, children, ...props}: React.ComponentProps<'div'>) { return ( <div data-slot='nvim-statusline-section-b' className={cn('inline-flex h-6 items-center', className)} {...props} > <div className={cn( 'inline-flex h-full items-center gap-2 bg-background pr-1 whitespace-nowrap', TEXT_COLORS, )} > {children} </div> <NvimStatuslineSectionSeparator data-slot='nvim-statusline-section-separator-2' className='size-6 rotate-90 bg-border fill-background' /> </div> )}function NvimStatuslineSectionC({ className, ...props}: React.ComponentProps<'div'>) { return ( <div data-slot='nvim-statusline-section-c' className={cn( 'inline-flex h-full max-w-full flex-1 items-center gap-2 truncate overflow-hidden text-ellipsis whitespace-nowrap', className, )} {...props} /> )}function NvimStatuslineSectionX({ className, ...props}: React.ComponentProps<'div'>) { return ( <div data-slot='nvim-statusline-section-x' className={cn( 'inline-flex h-full items-center gap-2 truncate overflow-hidden text-ellipsis whitespace-nowrap', className, )} {...props} /> )}function NvimStatuslineSectionY({ className, children, ...props}: React.ComponentProps<'div'>) { return ( <div data-slot='nvim-statusline-section-y' className={cn('inline-flex h-full items-center', className)} {...props} > <NvimStatuslineSectionSeparator data-slot='nvim-statusline-section-separator-2' className='size-6 -rotate-90 bg-border fill-background' /> <div className={cn( 'inline-flex h-full items-center gap-2 bg-background pl-1 whitespace-nowrap', TEXT_COLORS, )} > {children} </div> </div> )}function NvimStatuslineSectionZ({ className, children, ...props}: React.ComponentProps<'div'>) { return ( <div data-slot='nvim-statusline-section-z' className={cn('inline-flex h-full items-center', className)} {...props} > <NvimStatuslineSectionSeparator data-slot='nvim-statusline-section-separator-1' className={cn('size-6 -rotate-90 bg-background', FILL_COLORS)} /> <div className={cn( 'inline-flex h-full items-center gap-2 pr-2 pl-1 whitespace-nowrap text-background font-medium', BG_COLORS, )} > {children} </div> </div> )}function NvimStatuslineSectionSeparator({ ...props}: React.ComponentProps<'svg'>) { return ( <svg {...props} role='img' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' fill='currentColor' > <title>Nvim Statusline Section Separator</title> <path d='m12 11.25 12 12.784H0Z' /> </svg> )}export { NvimStatusline, NvimStatuslineSectionA, NvimStatuslineSectionB, NvimStatuslineSectionC, NvimStatuslineSectionX, NvimStatuslineSectionY, NvimStatuslineSectionZ, NvimStatuslineSectionSeparator, useNvimStatusline, NvimStatuslineProvider,}Structure
The NvimStatusline component follows a modular architecture with these key parts:
NvimStatuslineProvider- Context provider that manages statusline modes (normal, insert, visual, etc.)NvimStatusline- Main container component that renders the statusline layoutNvimStatuslineSection{A-Z}- Individual section components for organizing content within the statusline
The statusline is divided into six sections arranged as follows:
┌───┬───┬───────────────┬───┬───┬───┐
│ A │ B │ C │ X │ Y │ Z │
└───┴───┴───────────────┴───┴───┴───┘Usage
Initialize the statusline provider to enable mode management across your application
import { NvimStatuslineProvider } from '@/components/ui/nvim-statusline'
export default function RootLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
return (
<html lang='en'>
<body>
<NvimStatuslineProvider>{children}</NvimStatuslineProvider>
</body>
</html>
)
}Build your statusline layout using the provided section components
import {
NvimStatusline,
NvimStatuslineSectionA,
NvimStatuslineSectionB,
NvimStatuslineSectionC,
NvimStatuslineSectionX,
NvimStatuslineSectionY,
NvimStatuslineSectionZ,
} from '@/components/ui/nvim-statusline'
export function Statusline() {
return (
<NvimStatusline>
<NvimStatuslineSectionA />
<NvimStatuslineSectionB />
<NvimStatuslineSectionC />
<NvimStatuslineSectionX />
<NvimStatuslineSectionY />
<NvimStatuslineSectionZ />
</NvimStatusline>
)
}Integrate the statusline component into your application layout
import { Statusline } from '@/components/statusline.tsx'
import { NvimStatuslineProvider } from '@/components/ui/nvim-statusline'
export default function RootLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
return (
<html lang='en'>
<body>
<NvimStatuslineProvider>
{children}
<Statusline />
</NvimStatuslineProvider>
</body>
</html>
)
}Control statusline modes programmatically using the provided hook
const { setMode } = useNvimStatusline()
setMode('insert')If you don't use sections B or Y, add this class to NvimStatusline to hide the separators's background for sections A and Z:
<NvimStatusline className='[&_[data-slot=nvim-statusline-section-separator-1]]:bg-border' />References
- nvim-lualine/lualine.nvim - A blazing fast and easy to configure Neovim statusline plugin