diff --git a/apps/docs/docs/widgets/app/index.mdx b/apps/docs/docs/widgets/app/index.mdx
index fde54d943..2b0fd8313 100644
--- a/apps/docs/docs/widgets/app/index.mdx
+++ b/apps/docs/docs/widgets/app/index.mdx
@@ -14,6 +14,8 @@ import { appWidget } from ".";
The App widget opens an application or website and can show whether Homarr can reach it. Configure the target and status
check from the tile's edit actions.
+App icons, labels, and spacing adapt to the visible tile size. On small tiles, labels use at most two lines and shrink with the tile to leave room for the icon.
+
### Adding the widget
diff --git a/apps/docs/docs/widgets/health-monitoring/index.mdx b/apps/docs/docs/widgets/health-monitoring/index.mdx
index f1a50c3a3..3285cdf80 100644
--- a/apps/docs/docs/widgets/health-monitoring/index.mdx
+++ b/apps/docs/docs/widgets/health-monitoring/index.mdx
@@ -25,6 +25,8 @@ This widget displays information about the health and status of your system(s),
CPU usage, CPU temperature, memory usage, and GPU utilization are shown as compact gauges with threshold colors and their original units.
+Gauges switch to a smaller layout when the visible widget is narrow or short, including when fitting more columns or resizing the browser. Gauge labels stay proportional as the board scales.
+
Advanced view is a complete health workspace: it shows every available CPU, temperature, memory, GPU, filesystem, update, reboot, and system-information section even when compact visibility switches hide some of them on the board. Proxmox clusters likewise show uptime, CPU, memory, and every node, VM, container, and storage section. Storage-volume filters, units, and permissions remain in effect.
### Screenshots
diff --git a/packages/widgets/src/app/app.module.css b/packages/widgets/src/app/app.module.css
index b14962396..a470761a5 100644
--- a/packages/widgets/src/app/app.module.css
+++ b/packages/widgets/src/app/app.module.css
@@ -1,8 +1,16 @@
+.appText {
+ min-width: 0;
+ min-height: 0;
+ flex-shrink: 1;
+ overflow-wrap: anywhere;
+ overflow: hidden;
+}
+
.appIcon {
max-height: 100%;
max-width: 100%;
overflow: auto;
- flex: 1 !important;
+ flex: 1;
object-fit: contain;
scale: 0.8;
transition: scale 0.2s ease-in-out;
diff --git a/packages/widgets/src/app/component.tsx b/packages/widgets/src/app/component.tsx
index cb8c3cef6..474832b0d 100644
--- a/packages/widgets/src/app/component.tsx
+++ b/packages/widgets/src/app/component.tsx
@@ -2,7 +2,7 @@
import type { PropsWithChildren } from "react";
import { Fragment, Suspense } from "react";
-import { Box, Flex, rem, Stack, Text, Tooltip, UnstyledButton } from "@mantine/core";
+import { Box, Flex, Stack, Text, Tooltip, UnstyledButton } from "@mantine/core";
import { IconMinus } from "@tabler/icons-react";
import combineClasses from "clsx";
@@ -21,7 +21,13 @@ import classes from "./app.module.css";
import { PingDot } from "./ping/ping-dot";
import { PingIndicator } from "./ping/ping-indicator";
-export default function AppWidget({ options, isEditMode, height, width }: WidgetComponentProps<"app">) {
+export default function AppWidget({
+ options,
+ isEditMode,
+ height,
+ width,
+ displayScale = 1,
+}: WidgetComponentProps<"app">) {
const tCommon = useI18n("common");
const settings = useSettings();
const board = useRequiredBoard();
@@ -33,7 +39,13 @@ export default function AppWidget({ options, isEditMode, height, width }: Widget
if (isInitialWidgetQueryPending(appQuery)) return ;
if (!app) return ;
- const isTiny = height < 100 || width < 100;
+ // Readable board tokens stay fixed on screen, but must yield space to the icon
+ // when the tile itself becomes small. Below 100px, scale the whole composition.
+ let scale = 1;
+ if (Number.isFinite(displayScale) && displayScale > 0) scale = displayScale;
+ const contentScale = Math.min(1 / Math.min(scale, 1), Math.min(width, height) / 100);
+ const textSize = `${14 * contentScale}px`;
+ const spacing = 12 * contentScale;
const isColumnLayout = options.layout.startsWith("column");
return (
@@ -44,22 +56,26 @@ export default function AppWidget({ options, isEditMode, height, width }: Widget
enabled={options.descriptionDisplayMode === "tooltip" && Boolean(app.description) && !isEditMode}
>
e.preventDefault() : undefined}
>
-
+
{options.showTitle && (
{app.name}
@@ -68,10 +84,10 @@ export default function AppWidget({ options, isEditMode, height, width }: Widget
{options.descriptionDisplayMode === "normal" && (
{app.description?.split("\n").map((line, index) => (
@@ -88,9 +104,11 @@ export default function AppWidget({ options, isEditMode, height, width }: Widget
alt={app.name}
className={combineClasses(classes.appIcon, "app-icon")}
style={{
+ flex: isColumnLayout ? undefined : "0 0 50%",
height: "100%",
width: "100%",
- minWidth: "20%",
+ minWidth: 0,
+ minHeight: 0,
maxWidth: isColumnLayout ? undefined : "50%",
}}
/>
diff --git a/packages/widgets/src/health-monitoring/rings/cpu-ring.tsx b/packages/widgets/src/health-monitoring/rings/cpu-ring.tsx
index e192cc8eb..df6fed5f5 100644
--- a/packages/widgets/src/health-monitoring/rings/cpu-ring.tsx
+++ b/packages/widgets/src/health-monitoring/rings/cpu-ring.tsx
@@ -5,6 +5,7 @@ import { IconCpu } from "@tabler/icons-react";
import { zoomCompensatedSize } from "@homarr/ui";
import { progressColor } from "../system-health";
+import classes from "../system-health.module.css";
export const CpuRing = ({
cpuUtilization,
@@ -17,6 +18,7 @@ export const CpuRing = ({
}) => {
return (
multiline
>
& { withScrollArea?: boolean }) => {
@@ -75,7 +77,9 @@ export const SystemHealthMonitoring = ({
const { formatBytes, formatBytesPair } = useByteFormatter();
const isAdvanced = displayMode === "advanced";
- const isTiny = !isAdvanced && width < 256;
+ let scale = 1;
+ if (!isAdvanced && Number.isFinite(displayScale) && displayScale > 0) scale = displayScale;
+ const isTiny = !isAdvanced && (width * scale < 256 || height * scale < 160);
const showCpu = isAdvanced || options.cpu;
const showMemory = isAdvanced || options.memory;
const showGpu = isAdvanced || options.gpu;