Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Layout

Split View

Layout

Split View React component for Tailwind CSS v4

Settings LayoutToolbar
Live props
State
showDetail
Layout
defaultListWidth360px
minListWidth260px
maxListWidth560px
breakpoint
Behavior
resizable
persistKey
swipeBack
Content
emptyText
backLabel
resizeLabel
Appearance
showBackButton
bordered
Code
import { SplitView } from "@/components/ui/split-view"

<SplitView />
  1. Home
  2. Components
  3. Layout
  4. Split View
Pro

Split View React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React split view: a list and its detail side by side on wide screens, a push and pop stack with swipe back on phones.

On a wide screen pick a row and its detail fades in beside the list; both panes scroll on their own. Drag the hairline between them to resize the list (it follows the pointer and remembers the width with persistKey), double-click it to reset; focus it with Tab and use Left / Right to move it by 16px, Home / End for the limits and Enter to reset. Below the breakpoint the list fills the screen and a picked row pushes its detail in from the side while the list slides back under a dim; go back with the glass back bar, Escape, or by dragging the detail toward the edge with a finger (it follows the finger and completes past a quarter of the width or on a flick). Focus moves into the detail on push and back to the row on pop.

Split View is a Pro React component in the React layout components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add split-view, together with UIProvider, Button, usePersistedState, useScrollState and Motion presets and the npm packages motion and lucide-react.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add split-view

Split View is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import SplitView from @/components/ui/split-view.

import { useState } from "react"
import { SplitView } from "@/components/ui/split-view"

const clients = [
  { id: "tech", name: "Tech Solutions Inc.", city: "New York" },
  { id: "summit", name: "Summit Logistics", city: "Boston" },
  { id: "greenleaf", name: "Greenleaf Pharmacy", city: "Portland" },
]

export function Clients() {
  const [selected, setSelected] = useState<string | null>(null)
  const client = clients.find((c) => c.id === selected)

  return (
    <div className="h-dvh">
      <SplitView
        persistKey="crm:clients:split"
        backLabel="Clients"
        onBack={() => setSelected(null)}
        list={
          <ul className="p-2">
            {clients.map((c) => (
              <li key={c.id}>
                <button type="button" onClick={() => setSelected(c.id)} className="w-full rounded-2xl px-3 py-2.5 text-start">
                  {c.name}
                </button>
              </li>
            ))}
          </ul>
        }
        detail={
          client && (
            <article key={client.id} className="p-6">
              <h2 className="text-lg font-semibold">{client.name}</h2>
              <p className="text-sm text-muted-foreground">{client.city}</p>
            </article>
          )
        }
      />
    </div>
  )
}

Props

  • list*
    ReactNode

    The master pane: a list of clients, conversations or tickets. It scrolls on its own and keeps its scroll position while the detail is pushed over it.

  • detail
    ReactNode

    The detail pane's content. Give the element a key (the item id): a new key fades the content in and scrolls the pane back to the top, with no push.

  • showDetaildefault false
    boolean

    Whether the detail is open (on phones: pushed). Unset, it follows Boolean(detail). True without a detail shows the empty state.

  • onBack
    () => void

    Phones: called by the back button, a completed swipe and Escape. Clear the selection here (and call router.back() when the detail is a route).

  • defaultListWidthdefault 360
    number

    Starting width of the list column in px (also what a double-click or Enter on the divider resets to).

  • listWidth
    number

    Controlled width of the list column in px. Pair it with onListWidthChange.

  • onListWidthChange
    (width: number) => void

    Fires once when a resize ends (pointer up, a key press, a reset), never per frame.

  • minListWidthdefault 260
    number

    The narrowest the list can get, in px.

  • maxListWidthdefault 560
    number

    The widest the list can get, in px. A drag also leaves the detail at least minListWidth wide.

  • resizabledefault true
    boolean

    Drag the divider to resize; arrow keys, Home / End and Enter on the focused divider; double-click resets. Off, the divider is a plain hairline.

  • persistKeydefault ""
    string

    A storage key that remembers the list width across reloads (local storage). Empty keeps it in memory.

  • breakpointdefault "md"
    "md" | "lg"

    Below this width of the split view itself the panes stack: md (768px) or lg (1024px, tablets and narrow app-shell columns stack too). It is a CSS container query, so the server render is right and it also stacks inside a phone frame.

  • emptyTextdefault "Select an item to see it here"
    ReactNode

    The placeholder text in the detail pane while nothing is selected (wide screens).

  • emptyIcon
    ReactNode

    The placeholder icon. Defaults to MousePointerClick in a soft tile.

  • emptyDetail
    ReactNode

    Replaces the whole placeholder, e.g. a summary of the list or a call to action.

  • swipeBackdefault true
    boolean

    Phones: drag the detail toward the end to go back. It starts anywhere on the pane after an 8px horizontal slop, never inside [data-no-swipe], inputs or horizontal scrollers.

  • showBackButtondefault true
    boolean

    Phones: a sticky glass back bar at the top of the detail. Turn it off and put SplitViewBackButton (Button props plus label, default "Back"; calls onBack and hides itself while the panes sit side by side) in your own header. useSplitView() inside the split view returns { mode: "split" | "stack", showDetail, back }.

  • backLabeldefault "Back"
    ReactNode

    The back button's text. Use the list's name, like "Clients", as iOS does.

  • resizeLabeldefault "Resize list"
    string

    The accessible name of the divider.

  • bordereddefault true
    boolean

    The hairline between the panes. Off, the divider is invisible until hovered or dragged.

  • classNames
    Partial<Record<"list" | "detail" | "divider" | "back" | "empty", string>>

    Extra classes for the inner parts: the two scrolling panes, the divider, the phone back bar and the placeholder.

  • className
    string

    Extra classes for the root. It fills its parent's height (h-full), so give the parent a height.

Parts

  • SplitViewBackButton

    component

    A back button for your own detail header: it shows only while the panes are stacked and pops the detail like the built-in back bar.

    • labeldefault "Back"
      ReactNode

      The visible text, usually the list's name ("Clients").

    • variantdefault "ghost"
      ButtonVariant

      The Button variant.

    • sizedefault "sm"
      ButtonSize

      The Button size.

    • onClick
      (event: MouseEvent) => void

      Runs first; call event.preventDefault() to stop the pop.

  • useSplitView

    hook

    Inside a split view: { mode, showDetail, back }. mode is "split" or "stack" ("split" on the server) and back() pops the detail on phones.

Examples

Inbox

Conversations and a thread, with unread counts and a sticky composer.

Remembered width

persistKey keeps the list width in localStorage; reload and it is still there.

Empty detail

emptyDetail replaces the placeholder with a summary of the list.

On a phone

The split view measures itself: in a narrow frame the detail pushes over the list and swipes back.

Tablets stack too

breakpoint="lg" keeps the push / pop stack up to 1024px.

Fixed and borderless

resizable={false} and bordered={false}: two panes on one surface.

Dependencies

motionlucide-reactui-configbuttonuse-persisted-stateuse-scroll-statemotion

Files

  • components/ui/split-view.tsx

FAQ

Is the React Split View component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Split View?

Run npx wingo-ui@latest add split-view. It also adds the Wingo UI items it builds on (UIProvider, Button, usePersistedState, useScrollState and Motion presets) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/split-view.

See also

SheetDrawerFamily DrawerusePersistedStatehookList

Used in

Settings LayoutAutomation Builder PageInbox PageOrder History Page

More React layout components

ToolbarSeparatorCardApp ShellAccordionCollapsible
All 12 React layout components

Articles using this component

  • Order History Page Design: List, Detail and Actions

    Build it: screens and apps · Nov 6, 2026 · 9 min read

PreviousSettings LayoutNext Toolbar

Clients

20 clients · 3 overdue

Select an item to see it here

Drag the divider or focus it and use the arrow keys; double-click resets360px