Skip to main content
The renderer process is the React-based user interface layer. Each window runs in its own renderer process, communicating with the main process via IPC.

Technology Stack

React 19

Latest React with Compiler for automatic optimizations

TailwindCSS 4

Utility-first CSS with latest v4 features

Motion

Animations (Framer Motion successor)

Vite 7

Fast development and optimized builds
Package.json: package.json:95 (React 19.2.4)

Route-Based Architecture

Flow Browser uses a route-based structure where each window type has its own route. Routes Directory: src/renderer/src/routes/

Available Routes

Path: flow-internal://main-uiComponent: src/renderer/src/routes/main-ui/page.tsx:1The main browser chrome rendered in each browser window:
  • Tabbar with tab management
  • Address bar / omnibox
  • Sidebar with navigation
  • Window controls
  • Extension toolbar
import { BrowserUI } from "@/components/browser-ui/main";

function Page() {
  return <BrowserUI type="main" />;
}
Path: flow-internal://settingsComponent: src/renderer/src/routes/settings/page.tsxComplete settings interface:
  • General preferences
  • Privacy & security
  • Appearance customization
  • Spaces/profiles management
  • Keyboard shortcuts
  • Extensions management
Path: flow://new-tabComponent: src/renderer/src/routes/new-tab/page.tsxDefault new tab page with:
  • Search box
  • Shortcuts
  • Customizable background
Path: flow-internal://omniboxComponent: src/renderer/src/routes/omnibox/page.tsxFloating omnibox window shown when typing in address bar:
  • Search suggestions
  • History results
  • Bookmark matches
  • Calculator
  • Quick actions
Path: flow-internal://onboardingComponent: src/renderer/src/routes/onboarding/page.tsxFirst-run onboarding flow for new users.
Path: flow://extensionsComponent: src/renderer/src/routes/extensions/page.tsxChrome Web Store-style extension management interface.
Path: flow://pdf-viewerComponent: src/renderer/src/routes/pdf-viewer/page.tsxFull-featured PDF viewer using @pdfslick/react:
  • Zoom controls
  • Page navigation
  • Annotations
  • Search
  • Print

Component Architecture

Main Browser UI

The primary browser interface is built from composable components:
// src/renderer/src/components/browser-ui/main.tsx
import { Tabbar } from "@/components/browser-ui/tabbar";
import { Sidebar } from "@/components/browser-ui/sidebar";
import { WindowControls } from "@/components/window-controls";
import { Toolbar } from "@/components/browser-ui/toolbar";

export function BrowserUI({ type }: { type: "main" | "popup" }) {
  return (
    <div className="flex h-screen flex-col">
      <Tabbar />
      <Toolbar />
      <div className="flex flex-1">
        <Sidebar />
        {/* Web content rendered by main process */}
      </div>
      <WindowControls />
    </div>
  );
}

Component Categories

Location: src/renderer/src/components/browser-ui/Core browser interface components:
  • tabbar/ - Tab strip and tab components
  • toolbar/ - Address bar, navigation buttons
  • sidebar/ - Sidebar with bookmarks, history
  • window-controls/ - Platform-specific window controls (macOS, Linux, Windows)
  • update-effect.tsx - Update notification UI

Flow API Access

The renderer process accesses main process functionality through the Flow API exposed by preload.

API Structure

Type Definition: src/shared/flow/flow.ts:30
declare global {
  const flow: {
    // App APIs
    app: FlowAppAPI;
    windows: FlowWindowsAPI;
    extensions: FlowExtensionsAPI;
    updates: FlowUpdatesAPI;
    actions: FlowActionsAPI;
    shortcuts: FlowShortcutsAPI;
    
    // Browser APIs
    browser: FlowBrowserAPI;
    tabs: FlowTabsAPI;
    page: FlowPageAPI;
    navigation: FlowNavigationAPI;
    interface: FlowInterfaceAPI;
    omnibox: FlowOmniboxAPI;
    newTab: FlowNewTabAPI;
    findInPage: FlowFindInPageAPI;
    
    // Session APIs
    profiles: FlowProfilesAPI;
    spaces: FlowSpacesAPI;
    
    // Settings APIs
    settings: FlowSettingsAPI;
    icons: FlowIconsAPI;
    openExternal: FlowOpenExternalAPI;
    onboarding: FlowOnboardingAPI;
  };
}

Usage Examples

import { useState, useEffect } from 'react';

function Tabbar() {
  const [tabsData, setTabsData] = useState(null);
  
  // Subscribe to tab updates
  useEffect(() => {
    // Get initial data
    flow.tabs.getData().then(setTabsData);
    
    // Subscribe to changes
    const unsubscribe = flow.tabs.onDataUpdated((data) => {
      setTabsData(data);
    });
    
    return unsubscribe;
  }, []);
  
  // Switch to tab
  const handleTabClick = (tabId: number) => {
    flow.tabs.switchToTab(tabId);
  };
  
  // Close tab
  const handleTabClose = (tabId: number) => {
    flow.tabs.closeTab(tabId);
  };
  
  return (
    <div>
      {tabsData?.tabs.map(tab => (
        <Tab 
          key={tab.id}
          data={tab}
          onClick={() => handleTabClick(tab.id)}
          onClose={() => handleTabClose(tab.id)}
        />
      ))}
    </div>
  );
}

State Management

Flow Browser uses a hybrid state approach:

IPC Subscriptions

Tab state is managed in the main process and pushed to renderer via IPC:
// Subscribe to tab updates
flow.tabs.onDataUpdated((data: WindowTabsData) => {
  // Update React state
  setTabsData(data);
});

// Subscribe to content-only updates (lighter payload)
flow.tabs.onTabsContentUpdated((tabs: TabData[]) => {
  // Update only tab content properties
  setTabs(tabs);
});
Why: Tab state lives in main process (WebContentsView lifecycle), renderer reflects it.
UI-specific state (sidebar open, settings panel) uses standard React state:
const [sidebarOpen, setSidebarOpen] = useState(false);
const [selectedSetting, setSelectedSetting] = useState('general');
Why: Purely UI state with no main process involvement.
Settings are persisted in main process, accessed via IPC:
// Read setting
const value = await flow.settings.getSetting('appearance.theme');

// Write setting
await flow.settings.setSetting('appearance.theme', 'dark');

// Subscribe to changes
flow.settings.onSettingsChanged(() => {
  // Reload settings
});
Why: Settings need to persist across sessions and be accessible to main process.

Custom Hooks

Location: src/renderer/src/hooks/
File: src/renderer/src/hooks/use-debounce.tsxDebounce rapidly changing values:
const debouncedSearch = useDebounce(searchTerm, 300);

Styling with TailwindCSS 4

Flow Browser uses TailwindCSS 4 with the new Oxide engine.
Tailwind is configured via Vite plugin:
// Vite config
import tailwindcss from '@tailwindcss/vite';

export default {
  plugins: [
    tailwindcss()
  ]
};
Custom theme tokens defined in CSS:
@theme {
  --color-primary: oklch(0.5 0.2 200);
  --color-sidebar: oklch(0.95 0.01 200);
}

Component Styling Pattern

import { cn } from '@/lib/utils';
import { cva } from 'class-variance-authority';

const buttonVariants = cva(
  // Base styles
  'inline-flex items-center justify-center rounded-md font-medium transition-colors',
  {
    variants: {
      variant: {
        default: 'bg-primary text-primary-foreground hover:bg-primary/90',
        outline: 'border border-input hover:bg-accent',
        ghost: 'hover:bg-accent hover:text-accent-foreground',
      },
      size: {
        default: 'h-10 px-4 py-2',
        sm: 'h-9 px-3',
        lg: 'h-11 px-8',
      },
    },
    defaultVariants: {
      variant: 'default',
      size: 'default',
    },
  }
);

function Button({ variant, size, className, ...props }) {
  return (
    <button 
      className={cn(buttonVariants({ variant, size }), className)}
      {...props}
    />
  );
}

Animations with Motion

Flow uses Motion (Framer Motion v12) for animations. Package: package.json:91 (motion 12.34.3)
Import from motion/react, NOT framer-motion:
import { motion, AnimatePresence } from 'motion/react';

Animation Examples

import { motion, AnimatePresence } from 'motion/react';

function TabList({ tabs }) {
  return (
    <AnimatePresence mode="popLayout">
      {tabs.map(tab => (
        <motion.div
          key={tab.id}
          layout
          initial={{ opacity: 0, scale: 0.8 }}
          animate={{ opacity: 1, scale: 1 }}
          exit={{ opacity: 0, scale: 0.8 }}
          transition={{ duration: 0.2 }}
        >
          <Tab data={tab} />
        </motion.div>
      ))}
    </AnimatePresence>
  );
}

Build & Development

Development Mode

# Start dev server with hot reload
bun dev

# Start with file watching
bun dev:watch
Vite Config: electron.vite.config.ts

Production Build

# Type check and build
bun run build

# Build specific platform
bun run build:mac
bun run build:win
bun run build:linux
Production builds use PRODUCTION_BUILD=true environment variable to enable optimizations and tree-shaking.

React Compiler

Flow Browser uses the React Compiler for automatic optimizations. Plugin: package.json:73 (babel-plugin-react-compiler) The compiler automatically:
  • Memoizes components
  • Optimizes re-renders
  • Eliminates unnecessary useCallback/useMemo
No need for manual memoization in most cases - the compiler handles it!

Next Steps

IPC Communication

Learn how renderer and main process communicate

Main Process

Understand the backend architecture

Build docs developers (and LLMs) love