Platform Detection & Feature Detection Guide
This comprehensive guide covers platform detection, feature detection, and adaptive experiences in modern web applications using the Acrobi Design System.
Table of Contents
- Overview
- Platform Detection
- Feature Detection
- Feature Guard Component
- Best Practices
- Integration Patterns
- Live Examples
- Advanced Techniques
- Troubleshooting
Overview
Platform detection and feature detection are essential for creating adaptive web experiences that work seamlessly across different devices, operating systems, and browsers. The Acrobi Design System provides robust hooks and components for detecting platform capabilities and conditionally rendering content.
Key Components
usePlatform- Detects OS, browser, device type, and PWA statususeFeatureDetection- Checks for Web API supportFeatureGuard- Conditionally renders content based on feature support- Platform utilities - Helper functions for advanced platform detection
Platform Detection
The usePlatform hook provides comprehensive information about the user's platform environment.
Basic Usage
import { usePlatform } from '@acrobi/ui';
function MyComponent() {
const { platform, isReady } = usePlatform();
if (!isReady) {
return <div>Detecting platform...</div>;
}
return (
<div>
<p>OS: {platform.os}</p>
<p>Browser: {platform.browser}</p>
<p>Device: {platform.deviceType}</p>
<p>PWA: {platform.isPWA ? 'Yes' : 'No'}</p>
<p>Touch: {platform.hasTouch ? 'Yes' : 'No'}</p>
</div>
);
}Platform Information
The platform object contains:
interface PlatformInfo {
os: 'iOS' | 'Android' | 'Windows' | 'macOS' | 'Linux' | 'Unknown';
browser: 'Chrome' | 'Safari' | 'Firefox' | 'Edge' | 'Unknown';
deviceType: 'mobile' | 'tablet' | 'desktop' | 'unknown';
isPWA: boolean;
hasTouch: boolean;
isMobile: boolean;
isDesktop: boolean;
userAgent: string;
}Platform-Specific Adaptations
function AdaptiveInterface() {
const { platform } = usePlatform();
return (
<div className={`app ${platform.deviceType}`}>
{/* Mobile-optimized layout */}
{platform.isMobile && (
<div className="mobile-nav">
<TouchNavigation />
</div>
)}
{/* Desktop-enhanced features */}
{platform.isDesktop && (
<div className="desktop-features">
<KeyboardShortcuts />
<ContextMenus />
</div>
)}
{/* PWA-specific features */}
{platform.isPWA && (
<div className="pwa-features">
<OfflineIndicator />
<InstallPrompt />
</div>
)}
</div>
);
}Platform Utilities
import { PlatformUtils } from '@acrobi/ui';
function AdvancedPlatformFeatures() {
const { platform } = usePlatform();
// Check specific platform combinations
const isIOSSafari = PlatformUtils.matches(
{ os: 'iOS', browser: 'Safari' },
platform
);
// Get CSS classes for styling
const platformClasses = PlatformUtils.getCSSClasses(platform);
// Check API support
const hasServiceWorker = PlatformUtils.supportsAPI('serviceWorker');
// Get installation instructions
const installMethod = PlatformUtils.getInstallMethod(platform);
return (
<div className={platformClasses.join(' ')}>
{isIOSSafari && <IOSSpecificFeature />}
{hasServiceWorker && <OfflineCapability />}
<div className="install-instructions">
{installMethod}
</div>
</div>
);
}Feature Detection
The useFeatureDetection hook checks for specific Web API support.
Basic Usage
import { useFeatureDetection } from '@acrobi/ui';
function ShareButton() {
const { isSupported, isReady } = useFeatureDetection('webShare');
if (!isReady) {
return <div>Checking share support...</div>;
}
return isSupported ? (
<button onClick={handleWebShare}>Share</button>
) : (
<button onClick={handleFallbackShare}>Copy Link</button>
);
}Multiple Feature Detection
import { useMultipleFeatureDetection } from '@acrobi/ui';
function MediaCapture() {
const features = useMultipleFeatureDetection(['camera', 'microphone', 'mediaRecorder']);
const canRecordVideo = features.camera.isSupported &&
features.microphone.isSupported &&
features.mediaRecorder.isSupported;
return (
<div>
{canRecordVideo ? (
<VideoRecorder />
) : (
<div>
<p>Video recording requires:</p>
<ul>
<li>Camera: {features.camera.isSupported ? '✅' : '❌'}</li>
<li>Microphone: {features.microphone.isSupported ? '✅' : '❌'}</li>
<li>MediaRecorder: {features.mediaRecorder.isSupported ? '✅' : '❌'}</li>
</ul>
</div>
)}
</div>
);
}Feature Detection Utilities
import { FeatureDetectionUtils } from '@acrobi/ui';
function BrowserCapabilities() {
// Check multiple features at once
const features = FeatureDetectionUtils.checkFeatures([
'serviceWorker',
'pushManager',
'webShare',
'camera'
]);
// Get all supported features
const supportedFeatures = FeatureDetectionUtils.getSupportedFeatures();
// Get feature support score
const supportScore = FeatureDetectionUtils.getFeatureSupportScore();
// Check if browser is modern
const isModern = FeatureDetectionUtils.isModernBrowser();
// Get detailed capabilities
const capabilities = FeatureDetectionUtils.getBrowserCapabilities();
return (
<div>
<h3>Browser Support Score: {supportScore}%</h3>
<p>Modern Browser: {isModern ? 'Yes' : 'No'}</p>
<h4>Supported Features:</h4>
<ul>
{supportedFeatures.map(feature => (
<li key={feature}>{feature}</li>
))}
</ul>
<h4>Capabilities by Category:</h4>
{Object.entries(capabilities).map(([category, info]) => (
<div key={category}>
<h5>{category}: {info.supported}/{info.total}</h5>
<ul>
{Object.entries(info.features).map(([feature, supported]) => (
<li key={feature}>
{feature}: {supported ? '✅' : '❌'}
</li>
))}
</ul>
</div>
))}
</div>
);
}Feature Guard Component
The FeatureGuard component provides declarative conditional rendering based on feature support.
Basic Feature Guard
import { FeatureGuard } from '@acrobi/ui';
function MyApp() {
return (
<div>
{/* Only show if Web Share is supported */}
<FeatureGuard feature="webShare">
<ShareButton />
</FeatureGuard>
{/* Show different content based on support */}
<FeatureGuard
feature="webShare"
fallback={<CopyLinkButton />}
>
<NativeShareButton />
</FeatureGuard>
{/* Custom loading state */}
<FeatureGuard
feature="camera"
loadingComponent={<Spinner text="Checking camera..." />}
>
<CameraComponent />
</FeatureGuard>
</div>
);
}Multiple Feature Guard
import { MultipleFeatureGuard } from '@acrobi/ui';
function AdvancedFeatures() {
return (
<div>
{/* Require ALL features (AND logic) */}
<MultipleFeatureGuard
features={['camera', 'mediaRecorder']}
mode="all"
fallback={<div>Video recording not available</div>}
>
<VideoRecorderComponent />
</MultipleFeatureGuard>
{/* Require ANY feature (OR logic) */}
<MultipleFeatureGuard
features={['webShare', 'clipboard']}
mode="any"
fallback={<div>No sharing options available</div>}
>
<ShareOptionsComponent />
</MultipleFeatureGuard>
</div>
);
}Inverted Feature Guard
function LegacyBrowserWarning() {
return (
<FeatureGuard
feature="serviceWorker"
invert
fallback={<div>Service Worker is available</div>}
>
<div className="warning">
<h3>Browser Not Supported</h3>
<p>This app requires a modern browser with Service Worker support.</p>
<a href="/browser-support">Learn more</a>
</div>
</FeatureGuard>
);
}Higher-Order Component Pattern
import { withFeatureGuard } from '@acrobi/ui';
// Wrap component with feature guard
const EnhancedCameraComponent = withFeatureGuard(CameraComponent, {
feature: 'camera',
fallback: <div>Camera not available</div>
});
function App() {
return (
<div>
<EnhancedCameraComponent />
</div>
);
}Feature Detection Provider
import { FeatureDetectionProvider, useFeatureDetectionContext } from '@acrobi/ui';
function ChildComponent() {
const { checkFeature, getSupportedFeatures } = useFeatureDetectionContext();
const hasCamera = checkFeature('camera');
const supported = getSupportedFeatures();
return (
<div>
<p>Camera: {hasCamera ? 'Available' : 'Not available'}</p>
<p>Supported features: {supported.join(', ')}</p>
</div>
);
}
function App() {
return (
<FeatureDetectionProvider features={['camera', 'webShare', 'bluetooth']}>
<ChildComponent />
</FeatureDetectionProvider>
);
}Best Practices
1. Progressive Enhancement
Always design with progressive enhancement in mind:
function ProgressiveComponent() {
const { platform } = usePlatform();
const { isSupported: hasWebShare } = useFeatureDetection('webShare');
return (
<div>
{/* Base functionality that works everywhere */}
<BasicContent />
{/* Enhanced functionality for capable platforms */}
{platform.hasTouch && <TouchGestures />}
{hasWebShare && <NativeSharing />}
{platform.isPWA && <OfflineFeatures />}
</div>
);
}2. Graceful Degradation
Provide meaningful fallbacks:
function RobustFeature() {
return (
<FeatureGuard
feature="geolocation"
fallback={
<div>
<p>Location access not available</p>
<input
type="text"
placeholder="Enter your location manually"
/>
</div>
}
>
<LocationPicker />
</FeatureGuard>
);
}3. Performance Optimization
Cache feature detection results:
function OptimizedApp() {
// Pre-load commonly used features
const features = ['camera', 'webShare', 'clipboard', 'geolocation'];
return (
<FeatureDetectionProvider features={features}>
<AppContent />
</FeatureDetectionProvider>
);
}4. User Communication
Clearly communicate capabilities and limitations:
function TransparentFeature() {
const { isSupported, isReady, error } = useFeatureDetection('bluetooth');
if (!isReady) {
return <div>Checking Bluetooth support...</div>;
}
if (error) {
return <div>Unable to check Bluetooth support</div>;
}
return isSupported ? (
<BluetoothComponent />
) : (
<div>
<p>Bluetooth is not supported in your browser</p>
<details>
<summary>What can I do?</summary>
<ul>
<li>Use a Chromium-based browser (Chrome, Edge)</li>
<li>Enable experimental web platform features</li>
<li>Use the mobile app for full Bluetooth support</li>
</ul>
</details>
</div>
);
}Integration Patterns
1. Layout Adaptation
function AdaptiveLayout() {
const { platform } = usePlatform();
return (
<div className={`layout layout--${platform.deviceType}`}>
{platform.isMobile ? (
<MobileLayout />
) : (
<DesktopLayout />
)}
</div>
);
}2. Input Method Adaptation
function AdaptiveInput() {
const { platform } = usePlatform();
const { isSupported: hasSpeech } = useFeatureDetection('speechRecognition');
return (
<div>
<input type="text" placeholder="Type your message..." />
{platform.hasTouch && (
<button className="touch-optimized">
Touch Input
</button>
)}
{hasSpeech && (
<button className="voice-input">
🎤 Voice Input
</button>
)}
</div>
);
}3. Feature-Based Navigation
function AdaptiveNavigation() {
const { platform } = usePlatform();
const capabilities = FeatureDetectionUtils.getBrowserCapabilities();
const showAdvancedFeatures = capabilities['Hardware'].supported > 0;
return (
<nav>
<Link to="/">Home</Link>
<Link to="/basic">Basic Features</Link>
{showAdvancedFeatures && (
<Link to="/advanced">Advanced Features</Link>
)}
{platform.isPWA && (
<Link to="/offline">Offline Mode</Link>
)}
</nav>
);
}Live Examples
Example 1: Adaptive Media Capture
function MediaCaptureExample() {
const { platform } = usePlatform();
const camera = useFeatureDetection('camera');
const microphone = useFeatureDetection('microphone');
const mediaRecorder = useFeatureDetection('mediaRecorder');
const canRecordVideo = camera.isSupported && microphone.isSupported && mediaRecorder.isSupported;
const canTakePhoto = camera.isSupported;
return (
<div className="media-capture">
<h3>Media Capture Options</h3>
{canRecordVideo && (
<button onClick={startVideoRecording}>
📹 Record Video
</button>
)}
{canTakePhoto && (
<button onClick={takePhoto}>
📷 Take Photo
</button>
)}
{!canTakePhoto && !canRecordVideo && (
<div>
<p>Media capture not available</p>
<input type="file" accept="image/*,video/*" />
</div>
)}
{platform.isMobile && (
<p>Tip: Use the volume button as a shutter on mobile</p>
)}
</div>
);
}Example 2: Smart Sharing Component
function SmartSharingExample() {
const webShare = useFeatureDetection('webShare');
const clipboard = useFeatureDetection('clipboard');
const shareData = {
title: 'Check out this article',
text: 'Great content here!',
url: window.location.href
};
const handleShare = async () => {
if (webShare.isSupported) {
await navigator.share(shareData);
} else if (clipboard.isSupported) {
await navigator.clipboard.writeText(shareData.url);
alert('Link copied to clipboard!');
} else {
// Fallback to manual copy
prompt('Copy this link:', shareData.url);
}
};
return (
<div className="smart-sharing">
<button onClick={handleShare}>
{webShare.isSupported
? '📤 Share'
: clipboard.isSupported
? '📋 Copy Link'
: '🔗 Get Link'}
</button>
</div>
);
}Example 3: Offline-First App
function OfflineFirstExample() {
const { platform } = usePlatform();
const serviceWorker = useFeatureDetection('serviceWorker');
const backgroundSync = useFeatureDetection('backgroundSync');
return (
<div className="offline-app">
<header>
<h1>Offline-First App</h1>
{platform.isPWA && <PWAIndicator />}
</header>
<FeatureGuard
feature="serviceWorker"
fallback={
<div className="warning">
⚠️ Offline functionality not available
</div>
}
>
<OfflineContent />
<FeatureGuard feature="backgroundSync">
<BackgroundSyncFeatures />
</FeatureGuard>
</FeatureGuard>
</div>
);
}Advanced Techniques
1. Custom Feature Detection
// Create custom feature detectors
const customDetectors = {
// Check for specific CSS support
cssGridSupport: () => CSS.supports('display', 'grid'),
// Check for modern JavaScript features
asyncAwaitSupport: () => {
try {
eval('(async () => {})');
return true;
} catch {
return false;
}
},
// Check for specific vendor APIs
chromeFileSystemAccess: () => 'showOpenFilePicker' in window,
};
function CustomFeatureCheck() {
const [hasGridSupport, setHasGridSupport] = React.useState(false);
React.useEffect(() => {
setHasGridSupport(customDetectors.cssGridSupport());
}, []);
return (
<div className={hasGridSupport ? 'grid-layout' : 'flex-layout'}>
{/* Adaptive layout based on CSS support */}
</div>
);
}2. Performance Monitoring
function PerformanceAwareFeatures() {
const { platform } = usePlatform();
const [connectionSpeed, setConnectionSpeed] = React.useState('unknown');
React.useEffect(() => {
if ('connection' in navigator) {
const connection = (navigator as any).connection;
setConnectionSpeed(connection.effectiveType);
}
}, []);
// Adapt features based on device performance
const showHighQuality = platform.isDesktop && connectionSpeed !== 'slow-2g';
return (
<div>
{showHighQuality ? (
<HighQualityContent />
) : (
<OptimizedContent />
)}
</div>
);
}3. Context-Aware Adaptation
function ContextAwareApp() {
const { platform } = usePlatform();
const [isLowPowerMode, setIsLowPowerMode] = React.useState(false);
React.useEffect(() => {
// Detect power saving mode
if ('getBattery' in navigator) {
(navigator as any).getBattery().then((battery: any) => {
setIsLowPowerMode(battery.level < 0.2);
});
}
}, []);
return (
<div className={`app ${isLowPowerMode ? 'power-saving' : ''}`}>
{isLowPowerMode ? (
<LowPowerInterface />
) : (
<FullFeaturedInterface />
)}
</div>
);
}Troubleshooting
Common Issues
1. Feature Detection False Positives
Some browsers report API support but don't actually implement it properly:
function SafeFeatureDetection() {
const [actualSupport, setActualSupport] = React.useState<boolean | null>(null);
React.useEffect(() => {
async function testFeature() {
if ('serviceWorker' in navigator) {
try {
// Actually test the API
const registration = await navigator.serviceWorker.register('/test-sw.js');
await registration.unregister();
setActualSupport(true);
} catch {
setActualSupport(false);
}
} else {
setActualSupport(false);
}
}
testFeature();
}, []);
if (actualSupport === null) {
return <div>Testing feature support...</div>;
}
return actualSupport ? (
<ServiceWorkerFeatures />
) : (
<div>Service Worker not fully supported</div>
);
}2. Platform Detection Edge Cases
function RobustPlatformDetection() {
const { platform } = usePlatform();
const [actualPlatform, setActualPlatform] = React.useState(platform);
React.useEffect(() => {
// Additional checks for edge cases
const enhanced = {
...platform,
// Detect iPad running desktop Safari
isTablet: platform.deviceType === 'tablet' ||
(platform.os === 'macOS' && 'ontouchend' in document),
// Detect PWA more accurately
isPWA: platform.isPWA ||
window.matchMedia('(display-mode: standalone)').matches ||
(window.navigator as any).standalone === true
};
setActualPlatform(enhanced);
}, [platform]);
return <AdaptiveInterface platform={actualPlatform} />;
}3. SSR Compatibility
function SSRSafeComponent() {
const { platform, isReady } = usePlatform();
const [isClient, setIsClient] = React.useState(false);
React.useEffect(() => {
setIsClient(true);
}, []);
// Show neutral content during SSR
if (!isClient || !isReady) {
return <UniversalContent />;
}
// Show platform-specific content only on client
return (
<div>
{platform.isMobile ? <MobileContent /> : <DesktopContent />}
</div>
);
}Debugging Tips
- Use browser dev tools to inspect feature detection results
- Test on real devices as emulators may not accurately reflect API support
- Monitor console warnings for feature detection failures
- Use feature flags to gradually roll out platform-specific features
- Implement analytics to track feature usage across different platforms
Testing Strategy
// Mock platform detection for testing
const mockPlatform = {
os: 'iOS',
browser: 'Safari',
deviceType: 'mobile',
isPWA: true,
hasTouch: true,
isMobile: true,
isDesktop: false,
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)'
};
// Test component with different platform configurations
test('renders mobile interface on iOS', () => {
jest.mock('@acrobi/ui', () => ({
usePlatform: () => ({ platform: mockPlatform, isReady: true })
}));
render(<AdaptiveComponent />);
expect(screen.getByTestId('mobile-interface')).toBeInTheDocument();
});Conclusion
Platform detection and feature detection are powerful tools for creating adaptive, inclusive web experiences. By using the Acrobi Design System's hooks and components, you can build applications that gracefully adapt to different devices, browsers, and capabilities while maintaining a consistent user experience.
Remember to always:
- Design with progressive enhancement
- Provide meaningful fallbacks
- Test on real devices
- Monitor feature usage
- Keep user privacy and performance in mind
For more information, see the API documentation and component examples.