Platform Detection

Automatic Platform Detection and Forcing a Theme

The theme parameter takes 'auto' (the default), 'ios' or 'md'. With auto, Framework7 572,986 matches the user agent against iPhone, iPad and iPod patterns and gives those the iOS theme; everything else, desktop browsers included, gets Material. Because iPadOS 13 and later report a Mac user agent, a touch-screen MacIntel with an iPad screen size also counts as an iPad. The result lands in app.theme and as a class on <html>. BookNest's constructor now reads test switches from the URL (the imports are unchanged):

src/js/app.js: the constructor, with theme switches read from the URLJavaScript
// Test switches: ?theme=ios|md  &dark=1|0  &scheme=vibrant|monochrome  &color=%232e7d32
const query = new URLSearchParams(location.search);
const dark = query.get('dark');
const app = new Framework7({
  name: 'BookNest', el: '#app', component: App, store, routes,
  theme: query.get('theme') || 'auto',
  darkMode: dark === null ? 'auto' : dark === '1',
  mdColorScheme: query.get('scheme') || 'default',
  colors: { primary: query.get('color') || '#b3541e' },
});
window.app = app; // lets DevTools and test scripts reach the instance

This Playwright 26,280 script (pip 21,050 install playwright) tries three user agents, with and without ?theme=ios:

detect.py: which theme each user agent getsPython
from playwright.sync_api import sync_playwright
UA = {'iPhone': 'Mozilla/5.0 (iPhone; CPU iPhone OS 26_0 like Mac OS X)',
      'Android': 'Mozilla/5.0 (Linux; Android 16; Pixel 8)',
      'Desktop': None}  # None keeps Chrome's own Windows user agent
with sync_playwright() as p:
    browser = p.chromium.launch(channel='chrome')
    for query in ['', '?theme=ios']:
        for device, ua in UA.items():
            page = browser.new_page(user_agent=ua)
            page.goto('http://localhost:5294/' + query)
            page.wait_for_function('window.app && app.initialized')
            theme, cls = page.evaluate('[app.theme, [...document.documentElement.classList]]')
            cls = [c for c in cls if c == theme or 'device-' in c and 'ratio' not in c]
            print(f'{query or "(none)":11} {device:8} theme={theme:4} html: {" ".join(cls)}')
    browser.close()
Output
(none)      iPhone   theme=ios  html: ios device-ios
(none)      Android  theme=md   html: md device-android
(none)      Desktop  theme=md   html: md device-desktop device-windows
?theme=ios  iPhone   theme=ios  html: ios device-ios
?theme=ios  Android  theme=ios  html: ios device-android
?theme=ios  Desktop  theme=ios  html: ios device-desktop device-windows

Forcing leaves the device classes alone, and no method switches the theme later: reload instead. Where markup must differ, use the visibility classes: Material hides ios-only, if-ios and if-not-md elements, and iOS hides md-only, if-md and if-not-ios.