diff --git a/apps/remix-ide/src/app/tabs/locales/en/desktopDownload.json b/apps/remix-ide/src/app/tabs/locales/en/desktopDownload.json
new file mode 100644
index 00000000000..cfc956d218d
--- /dev/null
+++ b/apps/remix-ide/src/app/tabs/locales/en/desktopDownload.json
@@ -0,0 +1,14 @@
+{
+ "desktopDownload.loading": "Loading desktop app info...",
+ "desktopDownload.error": "Unable to load desktop app. Check the {link} for downloads.",
+ "desktopDownload.title": "Remix Desktop",
+ "desktopDownload.releaseDate": "Released {date}",
+ "desktopDownload.downloadSpan": "Download Remix Desktop {platform} {version}",
+ "desktopDownload.downloadSpanGeneric": "Download Remix Desktop {version}",
+ "desktopDownload.downloadCompactFull": "Download Remix Desktop {platform} {version}",
+ "desktopDownload.downloadCompactGeneric": "Download Remix Desktop {version}",
+ "desktopDownload.downloadButton": "Download for {platform}",
+ "desktopDownload.viewReleases": "View Downloads",
+ "desktopDownload.otherVersions": "Other versions and platforms",
+ "desktopDownload.noAutoDetect": "Available for Windows, macOS, and Linux"
+}
diff --git a/libs/remix-ui/desktop-download/README.md b/libs/remix-ui/desktop-download/README.md
new file mode 100644
index 00000000000..08a8f1d34bd
--- /dev/null
+++ b/libs/remix-ui/desktop-download/README.md
@@ -0,0 +1,139 @@
+# Desktop Download Component
+
+A React component for downloading the latest Remix Desktop application from GitHub releases.
+
+## Features
+
+- **Auto OS Detection**: Automatically detects the user's operating system (Windows, macOS, Linux) and suggests the appropriate download
+- **Architecture Support**: Detects Apple Silicon Macs and provides ARM64 builds when available
+- **Smart Caching**: Caches release data for 30 minutes to reduce API calls
+- **Internationalization**: Fully supports i18n with FormattedMessage components
+- **Responsive Design**: Works on both desktop and mobile devices
+- **Error Handling**: Graceful fallback when GitHub API is unavailable
+
+## Usage
+
+```tsx
+import { DesktopDownload } from '@remix-ui/desktop-download'
+
+function MyComponent() {
+ return (
+
+ {/* Compact layout with tracking */}
+
+
+ {/* Full layout */}
+
+
+ {/* Span variant for dropdowns */}
+
+
+ )
+}
+```
+
+### Compact Layout (Default)
+Perfect for navigation bars, toolbars, or anywhere space is limited. Shows a button with "Download Remix Desktop {OS} {version}" and a small muted link to other releases below.
+
+```tsx
+
+
+```
+
+### Full Layout
+Great for landing pages, cards, or dedicated download sections. Shows detailed information including release date, file size, and platform-specific icons.
+
+```tsx
+
+```
+
+### Span Variant
+Perfect for dropdown items or anywhere you need a simple link without button styling.
+
+```tsx
+
+```
+
+### With custom styling
+
+```tsx
+
+
+
+```
+
+## Props
+
+| Prop | Type | Default | Description |
+|------|------|---------|-------------|
+| `className` | `string` | `''` | Additional CSS classes |
+| `compact` | `boolean` | `true` | Use compact layout |
+| `variant` | `'button' \| 'span'` | `'button'` | Display variant |
+| `style` | `CSSProperties` | `{}` | Inline styles |
+| `trackingContext` | `string` | `'unknown'` | Context for Matomo analytics (e.g., 'hometab', 'dropdown', 'navbar') |
+
+## Analytics Tracking
+
+The component includes automatic Matomo analytics tracking for all download interactions. Set the `trackingContext` prop to identify where the component is used:
+
+```tsx
+
+```
+
+**Tracking Events:**
+- Category: `desktopDownload`
+- Action: `{context}-{variant}` (e.g., `hometab-compact`, `dropdown-span`)
+- Name: `{platform}-{filename}` or `releases-page` for fallbacks
+
+**Examples:**
+- `['trackEvent', 'desktopDownload', 'hometab-compact', 'linux-remix-desktop_1.1.0_amd64.deb']`
+- `['trackEvent', 'desktopDownload', 'dropdown-span', 'windows-remix-desktop-1.1.0-setup.exe']`
+- `['trackEvent', 'desktopDownload', 'navbar-full-fallback', 'releases-page']`
+
+## Platform Support
+
+The component automatically detects and provides downloads for:
+
+- **Windows**: `.exe` installer
+- **macOS**: `.dmg` disk image (ARM64 for Apple Silicon, Intel for older Macs)
+- **Linux**: `.deb` package (with `.AppImage` fallback)
+
+## API
+
+The component fetches release data from:
+`https://api.github.com/repos/remix-project-org/remix-desktop/releases/latest`
+
+## Caching
+
+Release data is cached in localStorage for 30 minutes to reduce GitHub API calls and improve performance.
+
+## Dependencies
+
+- React 18+
+- @remix-ui/helper (for CustomTooltip)
+- react-intl (for internationalization)
+- Bootstrap CSS classes (for styling)
+- FontAwesome icons (for platform icons)
+
+## Internationalization
+
+The component is fully internationalized using react-intl. Translation strings are located in:
+
+- `apps/remix-ide/src/app/tabs/locales/en/desktopDownload.json` - All translation keys for the component
+
+### Translation Keys
+
+| Key | Default Message | Description |
+|-----|-----------------|-------------|
+| `desktopDownload.loading` | "Loading desktop app info..." | Loading state message |
+| `desktopDownload.error` | "Unable to load desktop app. Check the {link} for downloads." | Error fallback message |
+| `desktopDownload.title` | "Remix Desktop" | Main title in full layout |
+| `desktopDownload.releaseDate` | "Released {date}" | Release date display |
+| `desktopDownload.downloadSpan` | "Download Remix Desktop {platform} {version}" | Span variant with platform |
+| `desktopDownload.downloadSpanGeneric` | "Download Remix Desktop {version}" | Span variant without platform |
+| `desktopDownload.downloadCompactFull` | "Download Remix Desktop {platform} {version}" | Compact button with platform |
+| `desktopDownload.downloadCompactGeneric` | "Download Remix Desktop {version}" | Compact button without platform |
+| `desktopDownload.downloadButton` | "Download for {platform}" | Full layout button text |
+| `desktopDownload.viewReleases` | "View Downloads" | Fallback button text |
+| `desktopDownload.otherVersions` | "Other versions and platforms" | Link to releases page |
+| `desktopDownload.noAutoDetect` | "Available for Windows, macOS, and Linux" | Platform availability message |
diff --git a/libs/remix-ui/desktop-download/index.ts b/libs/remix-ui/desktop-download/index.ts
new file mode 100644
index 00000000000..5f59ba73223
--- /dev/null
+++ b/libs/remix-ui/desktop-download/index.ts
@@ -0,0 +1 @@
+export * from './lib/desktop-download'
\ No newline at end of file
diff --git a/libs/remix-ui/desktop-download/lib/desktop-download.css b/libs/remix-ui/desktop-download/lib/desktop-download.css
new file mode 100644
index 00000000000..2e402d4b5a3
--- /dev/null
+++ b/libs/remix-ui/desktop-download/lib/desktop-download.css
@@ -0,0 +1,98 @@
+.desktop-download {
+ max-width: 350px;
+}
+
+.desktop-download.compact {
+ max-width: none;
+}
+
+.desktop-download.compact .btn {
+ white-space: normal; /* Allow text wrapping */
+ text-align: center;
+ line-height: 1.3;
+ min-height: 2.5rem; /* Ensure consistent button height */
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.desktop-download.compact .text-muted {
+ font-size: 0.75rem;
+ text-align: center;
+}
+
+.desktop-download.span-variant {
+ max-width: none;
+}
+
+.desktop-download.span-variant a {
+ color: inherit !important;
+ text-decoration: none !important;
+ position: relative;
+ z-index: 10;
+}
+
+.desktop-download.span-variant a:hover {
+ color: inherit !important;
+ opacity: 0.8;
+}
+
+.desktop-download.span-variant .d-flex {
+ pointer-events: auto;
+}
+
+.desktop-download .btn {
+ border-radius: 8px;
+ font-weight: 500;
+ transition: all 0.2s ease;
+ white-space: normal; /* Allow text wrapping on all buttons */
+ text-align: center;
+ line-height: 1.3;
+}
+
+.desktop-download .btn:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.desktop-download .badge {
+ font-size: 0.75rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: 4px;
+}
+
+.desktop-download .text-muted a:hover {
+ text-decoration: underline !important;
+}
+
+.desktop-download .spinner-border-sm {
+ width: 1rem;
+ height: 1rem;
+}
+
+.desktop-download .fab,
+.desktop-download .fas,
+.desktop-download .far {
+ min-width: 1.2rem;
+}
+
+/* Dark mode support - removed custom colors to use app defaults */
+
+@media (max-width: 768px) {
+ .desktop-download.full {
+ max-width: 100%;
+ }
+
+ .desktop-download.full .btn {
+ width: 100%;
+ justify-content: center;
+ }
+
+ .desktop-download.full h5 {
+ font-size: 1.1rem;
+ }
+
+ .desktop-download.compact .btn {
+ font-size: 0.875rem;
+ }
+}
diff --git a/libs/remix-ui/desktop-download/lib/desktop-download.tsx b/libs/remix-ui/desktop-download/lib/desktop-download.tsx
new file mode 100644
index 00000000000..e03ce39dfd9
--- /dev/null
+++ b/libs/remix-ui/desktop-download/lib/desktop-download.tsx
@@ -0,0 +1,522 @@
+import React, { useState, useEffect } from 'react'
+import { CustomTooltip } from '@remix-ui/helper'
+import { FormattedMessage } from 'react-intl'
+import './desktop-download.css'
+
+const _paq = (window._paq = window._paq || []) // eslint-disable-line
+
+interface DesktopDownloadProps {
+ className?: string
+ compact?: boolean
+ variant?: 'button' | 'span' | 'auto'
+ style?: React.CSSProperties
+ trackingContext?: string // Context for Matomo tracking (e.g., 'hometab', 'dropdown', 'navbar')
+}
+
+interface ReleaseAsset {
+ name: string
+ browser_download_url: string
+ size: number
+}
+
+interface ReleaseData {
+ tag_name: string
+ name: string
+ published_at: string
+ html_url: string
+ assets: ReleaseAsset[]
+}
+
+interface DetectedDownload {
+ url: string
+ filename: string
+ platform: string
+ size: number
+}
+
+const GITHUB_API_URL = 'https://api.github.com/repos/remix-project-org/remix-desktop/releases/latest'
+const CACHE_KEY = 'remix-desktop-release-cache'
+const CACHE_DURATION = 30 * 60 * 1000 // 30 minutes in milliseconds
+
+export const DesktopDownload: React.FC = ({
+ className = '',
+ compact = true,
+ variant = 'button',
+ style = {},
+ trackingContext = 'unknown'
+}) => {
+ const [releaseData, setReleaseData] = useState(null)
+ const [loading, setLoading] = useState(true)
+ const [error, setError] = useState(null)
+ const [detectedDownload, setDetectedDownload] = useState(null)
+
+ // Detect user's operating system
+ const detectOS = (): 'windows' | 'macos' | 'linux' => {
+ const userAgent = navigator.userAgent.toLowerCase()
+ if (userAgent.includes('win')) return 'windows'
+ if (userAgent.includes('mac')) return 'macos'
+ return 'linux'
+ }
+
+ // Check if cached data is still valid
+ const getCachedData = (): ReleaseData | null => {
+ try {
+ const cached = localStorage.getItem(CACHE_KEY)
+ if (!cached) return null
+
+ const { data, timestamp } = JSON.parse(cached)
+ if (Date.now() - timestamp > CACHE_DURATION) {
+ localStorage.removeItem(CACHE_KEY)
+ return null
+ }
+
+ return data
+ } catch {
+ localStorage.removeItem(CACHE_KEY)
+ return null
+ }
+ }
+
+ // Cache the release data
+ const setCachedData = (data: ReleaseData) => {
+ try {
+ localStorage.setItem(CACHE_KEY, JSON.stringify({
+ data,
+ timestamp: Date.now()
+ }))
+ } catch {
+ // Ignore storage errors
+ }
+ }
+
+ // Fetch release data from GitHub API
+ const fetchReleaseData = async (): Promise => {
+ const response = await fetch(GITHUB_API_URL)
+ if (!response.ok) {
+ throw new Error(`Failed to fetch release data: ${response.status}`)
+ }
+ return response.json()
+ }
+
+ // Find the appropriate download for the user's OS
+ const findDownloadForOS = (assets: ReleaseAsset[], os: string): DetectedDownload | null => {
+ let preferredAsset: ReleaseAsset | null = null
+
+ if (os === 'windows') {
+ preferredAsset = assets.find(asset =>
+ asset.name.toLowerCase().includes('setup') &&
+ asset.name.toLowerCase().includes('.exe')
+ ) || null
+ } else if (os === 'macos') {
+ // Check if user is on Apple Silicon (M1/M2/etc.)
+ const isAppleSilicon = navigator.userAgent.includes('Macintosh') &&
+ (navigator.userAgent.includes('ARM') ||
+ /Mac OS X 10_1[5-9]|Mac OS X 1[1-9]|macOS/.test(navigator.userAgent))
+
+ // Prefer ARM64 version for newer Macs, Intel version for older ones
+ if (isAppleSilicon) {
+ preferredAsset = assets.find(asset =>
+ asset.name.toLowerCase().includes('.dmg') &&
+ asset.name.toLowerCase().includes('arm64')
+ ) || null
+ }
+
+ // Fallback to any dmg if ARM64 not found or not Apple Silicon
+ if (!preferredAsset) {
+ preferredAsset = assets.find(asset =>
+ asset.name.toLowerCase().includes('.dmg') &&
+ !asset.name.toLowerCase().includes('arm64')
+ ) || assets.find(asset =>
+ asset.name.toLowerCase().includes('.dmg')
+ ) || null
+ }
+ } else if (os === 'linux') {
+ // Prefer .deb for most Linux distributions
+ preferredAsset = assets.find(asset =>
+ asset.name.toLowerCase().includes('.deb')
+ ) || null
+
+ // Fallback to AppImage if no .deb found
+ if (!preferredAsset) {
+ preferredAsset = assets.find(asset =>
+ asset.name.toLowerCase().includes('.appimage')
+ ) || null
+ }
+ }
+
+ if (!preferredAsset) return null
+
+ return {
+ url: preferredAsset.browser_download_url,
+ filename: preferredAsset.name,
+ platform: os,
+ size: preferredAsset.size
+ }
+ }
+
+ // Format file size
+ const formatSize = (bytes: number): string => {
+ const mb = bytes / (1024 * 1024)
+ return `${mb.toFixed(1)} MB`
+ }
+
+ // Format date
+ const formatDate = (dateString: string): string => {
+ const date = new Date(dateString)
+ return date.toLocaleDateString(undefined, {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric'
+ })
+ }
+
+ // Get platform-specific icon
+ const getPlatformIcon = (platform: string): string => {
+ switch (platform) {
+ case 'windows': return 'fab fa-windows'
+ case 'macos': return 'fab fa-apple'
+ case 'linux': return 'fab fa-linux'
+ default: return 'fas fa-desktop'
+ }
+ }
+
+ // Get platform display name
+ const getPlatformName = (platform: string): string => {
+ switch (platform) {
+ case 'windows': return 'Windows'
+ case 'macos': return 'macOS'
+ case 'linux': return 'Linux'
+ default: return platform.charAt(0).toUpperCase() + platform.slice(1)
+ }
+ }
+
+ // Track download click events
+ const trackDownloadClick = (platform?: string, filename?: string, variant?: string) => {
+ const trackingData = [
+ 'trackEvent',
+ 'desktopDownload',
+ `${trackingContext}-${variant || 'button'}`,
+ platform ? `${platform}-${filename}` : 'releases-page'
+ ]
+ _paq.push(trackingData)
+ }
+
+ // Load release data on component mount
+ useEffect(() => {
+ const loadReleaseData = async () => {
+ try {
+ setLoading(true)
+ setError(null)
+
+ // Try to get cached data first
+ const cached = getCachedData()
+ if (cached) {
+ setReleaseData(cached)
+ const os = detectOS()
+ const download = findDownloadForOS(cached.assets, os)
+ setDetectedDownload(download)
+ setLoading(false)
+ return
+ }
+
+ // Fetch fresh data
+ const data = await fetchReleaseData()
+ setReleaseData(data)
+ setCachedData(data)
+
+ // Find appropriate download for user's OS
+ const os = detectOS()
+ const download = findDownloadForOS(data.assets, os)
+ setDetectedDownload(download)
+
+ } catch (err) {
+ console.error('Failed to load release data:', err)
+ setError(err instanceof Error ? err.message : 'Failed to load release data')
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ loadReleaseData()
+ }, [])
+
+ if (loading) {
+ return (
+
+ )
+ }
+
+ if (error || !releaseData) {
+ return (
+
+
+
+ releases page
+
+ )
+ }}
+ />
+
+
+ )
+ }
+
+ return (
+
+ {variant === 'span' ? (
+ // Span variant - for use in dropdown items
+
+ ) : compact ? (
+ // Compact layout - single line with additional info below
+
+ ) : (
+ // Full layout - original multi-line design
+
+
+
+
+
+ {releaseData.tag_name}
+
+
+
+
+
+
+
+
+ {detectedDownload ? (
+
+ ) : (
+
+ )}
+
+ )}
+
+ )
+}
+
+export default DesktopDownload
diff --git a/libs/remix-ui/home-tab/src/lib/components/homeTabTitle.tsx b/libs/remix-ui/home-tab/src/lib/components/homeTabTitle.tsx
index 1f9222ad8cf..843c9ebd38b 100644
--- a/libs/remix-ui/home-tab/src/lib/components/homeTabTitle.tsx
+++ b/libs/remix-ui/home-tab/src/lib/components/homeTabTitle.tsx
@@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl'
import { CustomTooltip } from '@remix-ui/helper'
import { ThemeContext } from '../themeContext'
import { Placement } from 'react-bootstrap/esm/types'
+import { DesktopDownload } from 'libs/remix-ui/desktop-download' // eslint-disable-line @nrwl/nx/enforce-module-boundaries
const _paq = (window._paq = window._paq || []) // eslint-disable-line
type HometabIconSection = {
@@ -115,6 +116,7 @@ function HomeTabTitle() {
_paq.push(['trackEvent', 'hometab', 'titleCard', 'documentation'])}>
_paq.push(['trackEvent', 'hometab', 'titleCard', 'webSite'])}>
+
)
diff --git a/libs/remix-ui/top-bar/src/components/WorkspaceDropdown.tsx b/libs/remix-ui/top-bar/src/components/WorkspaceDropdown.tsx
index 21d705a1a7b..a71c318e7b2 100644
--- a/libs/remix-ui/top-bar/src/components/WorkspaceDropdown.tsx
+++ b/libs/remix-ui/top-bar/src/components/WorkspaceDropdown.tsx
@@ -7,6 +7,7 @@ import { FiMoreVertical } from 'react-icons/fi'
import { TopbarContext } from '../context/topbarContext'
import { getWorkspaces } from 'libs/remix-ui/workspace/src/lib/actions'
import { WorkspaceMetadata } from 'libs/remix-ui/workspace/src/lib/types'
+import { DesktopDownload } from 'libs/remix-ui/desktop-download'
interface Branch {
name: string
@@ -326,19 +327,8 @@ export const WorkspacesDropdown: React.FC = ({ menuItem
- {
- window.open('https://github.com/remix-project-org/remix-desktop/releases', '_blank')
- setShowMain(false)
- setOpenSub(null)
- }}>
- {
- window.open('https://github.com/remix-project-org/remix-desktop/releases', '_blank')
- setShowMain(false)
- setOpenSub(null)
- }}>
-
- Download Remix Desktop
-
+
+
{
downloadWorkspaces()