← Articles

Flutter desktop: building for macOS and Windows

By Ann Tech · 24 December 2025

Flutter's desktop support left beta in Flutter 3.0. macOS and Windows apps built with Flutter share virtually all business logic and UI with your mobile app — but desktop introduces constraints and interactions that mobile doesn't have: window resizing, keyboard navigation, right-click context menus, and file system access.

Enabling desktop targets

flutter config --enable-macos-desktop
flutter config --enable-windows-desktop

# Create or add desktop support to an existing project
flutter create --platforms=macos,windows .

This adds macos/ and windows/ directories alongside android/ and ios/.

Responsive layouts for desktop

Mobile layouts assume a narrow portrait screen. Desktop windows can be 1440px wide and resized at any time. Use LayoutBuilder to adapt:

class AdaptiveLayout extends StatelessWidget {
  const AdaptiveLayout({super.key, required this.body});
  final Widget body;

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        if (constraints.maxWidth >= 1200) {
          return _WideLayout(body: body);
        } else if (constraints.maxWidth >= 600) {
          return _MediumLayout(body: body);
        } else {
          return _NarrowLayout(body: body);
        }
      },
    );
  }
}

class _WideLayout extends StatelessWidget {
  const _WideLayout({required this.body});
  final Widget body;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        const SizedBox(width: 260, child: AppSidebar()),
        Expanded(child: body),
      ],
    );
  }
}

For desktop, a sidebar navigation (NavigationRail or custom) replaces the BottomNavigationBar.

Keyboard shortcuts

Desktop users expect keyboard shortcuts. Wire them with Shortcuts and Actions:

class AppShortcuts extends StatelessWidget {
  const AppShortcuts({super.key, required this.child});
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Shortcuts(
      shortcuts: {
        LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyN):
            const NewDocumentIntent(),
        LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyS):
            const SaveIntent(),
        LogicalKeySet(LogicalKeyboardKey.escape):
            const DismissIntent(),
      },
      child: Actions(
        actions: {
          NewDocumentIntent: CallbackAction<NewDocumentIntent>(
            onInvoke: (_) => context.read<DocumentBloc>().add(const NewDocument()),
          ),
          SaveIntent: CallbackAction<SaveIntent>(
            onInvoke: (_) => context.read<DocumentBloc>().add(const SaveDocument()),
          ),
        },
        child: Focus(autofocus: true, child: child),
      ),
    );
  }
}

Right-click context menus

class ContextMenuWrapper extends StatelessWidget {
  const ContextMenuWrapper({super.key, required this.child, required this.items});
  final Widget child;
  final List<ContextMenuEntry> items;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onSecondaryTapDown: (details) {
        showMenu(
          context: context,
          position: RelativeRect.fromLTRB(
            details.globalPosition.dx,
            details.globalPosition.dy,
            details.globalPosition.dx + 1,
            details.globalPosition.dy + 1,
          ),
          items: items.map((e) => PopupMenuItem(
            onTap: e.onTap,
            child: Row(
              children: [
                Icon(e.icon, size: 16),
                const SizedBox(width: 8),
                Text(e.label),
              ],
            ),
          )).toList(),
        );
      },
      child: child,
    );
  }
}

File system access

For file open/save dialogs, use file_picker:

dependencies:
  file_picker: ^8.0.0
Future<void> openFile() async {
  final result = await FilePicker.platform.pickFiles(
    type: FileType.custom,
    allowedExtensions: ['json', 'yaml'],
  );
  if (result == null) return;
  final path = result.files.single.path!;
  final content = await File(path).readAsString();
  // process content
}

Future<void> saveFile(String content) async {
  final path = await FilePicker.platform.saveFile(
    dialogTitle: 'Save file',
    fileName: 'export.json',
  );
  if (path == null) return;
  await File(path).writeAsString(content);
}

Window management

Control the window size and title with window_manager:

dependencies:
  window_manager: ^0.3.0
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await windowManager.ensureInitialized();

  const options = WindowOptions(
    size: Size(1200, 800),
    minimumSize: Size(800, 600),
    center: true,
    title: 'My Desktop App',
    titleBarStyle: TitleBarStyle.hidden, // Custom title bar
  );

  await windowManager.waitUntilReadyToShow(options, () async {
    await windowManager.show();
    await windowManager.focus();
  });

  runApp(const MyApp());
}

Platform-specific code

Detect the platform and adapt:

import 'dart:io';

Widget buildNavigation(Widget body) {
  if (Platform.isWindows || Platform.isMacOS || Platform.isLinux) {
    return Row(
      children: [const Sidebar(), Expanded(child: body)],
    );
  }
  return Scaffold(
    body: body,
    bottomNavigationBar: const AppBottomNav(),
  );
}

Common pitfalls

Touch-only interactions. Drag-to-dismiss drawers, swipe-to-delete, and long-press context menus don't work well with a mouse. Add hover states, right-click menus, and keyboard alternatives.

Small tap targets. Mobile buttons sized for fingers (48dp) look oversized on desktop. Adapt sizes: isDesktop ? 32.0 : 48.0.

Missing scrollbar. Desktop users expect visible scrollbars. Wrap ListView with Scrollbar(thumbVisibility: true, ...).

No window restore. Save window size and position to shared_preferences and restore on next launch — desktop users expect this.

macOS entitlements. macOS apps run in a sandbox. File access, network requests, and camera use require explicit entitlements in macos/Runner/DebugProfile.entitlements and ReleaseProfile.entitlements.

Sign in to like, dislike, or report.

Flutter desktop: building for macOS and Windows — ANN Tech