diff --git a/.github/pr-assets/bookmarks/after.png b/.github/pr-assets/bookmarks/after.png
new file mode 100644
index 000000000..27d07abb4
Binary files /dev/null and b/.github/pr-assets/bookmarks/after.png differ
diff --git a/.github/pr-assets/bookmarks/before.png b/.github/pr-assets/bookmarks/before.png
new file mode 100644
index 000000000..06ab10a74
Binary files /dev/null and b/.github/pr-assets/bookmarks/before.png differ
diff --git a/apps/docs/docs/widgets/bookmarks/index.mdx b/apps/docs/docs/widgets/bookmarks/index.mdx
index d712c4a9d..9c22f1bae 100644
--- a/apps/docs/docs/widgets/bookmarks/index.mdx
+++ b/apps/docs/docs/widgets/bookmarks/index.mdx
@@ -18,7 +18,7 @@ Paste a URL and press Enter, paste several comma-separated or one-per-line URLs,
URLs do not need an App record; Homarr uses the site's favicon and falls back to a link icon when it cannot load one.
Bookmarks selected from Apps continue to use the App name, URL, icon, and description.
-**Adaptive** layout is the practical default: it changes between icons, compact rows, and tiles as the widget changes shape. Every compact item exposes its destination on hover, while advanced view expands items into descriptive cards.
+**Adaptive** layout is the practical default: it changes between icons, compact rows, and tiles as the widget changes shape. **Compact grid** keeps every card to a short row. Enable **Expand cards to fill row** when cards in an incomplete final row should use the remaining width. Every compact item exposes its destination on hover, while advanced view expands items into descriptive cards.
### Screenshots
diff --git a/apps/docs/docs/widgets/bookmarks/index.ts b/apps/docs/docs/widgets/bookmarks/index.ts
index b1a766ade..b335a70fd 100644
--- a/apps/docs/docs/widgets/bookmarks/index.ts
+++ b/apps/docs/docs/widgets/bookmarks/index.ts
@@ -56,6 +56,12 @@ export const bookmarksWidget: WidgetDefinition = {
values: { type: "boolean" },
defaultValue: "yes",
},
+ {
+ name: "Expand cards to fill row",
+ description: "Whether cards in an incomplete final row expand to use the remaining width.",
+ values: { type: "boolean" },
+ defaultValue: "no",
+ },
{
name: "Bookmarks",
description: "URLs and existing Apps shown by the widget.",
diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json
index 04dabe9f7..8a37b40d7 100644
--- a/packages/translation/src/lang/en.json
+++ b/packages/translation/src/lang/en.json
@@ -2272,6 +2272,9 @@
"withBorder": {
"label": "Show border"
},
+ "grow": {
+ "label": "Expand cards to fill row"
+ },
"customUrls": {
"label": "Custom links"
},
diff --git a/packages/widgets/src/bookmarks/bookmarks-widget.tsx b/packages/widgets/src/bookmarks/bookmarks-widget.tsx
index cc73a445a..5838af748 100644
--- a/packages/widgets/src/bookmarks/bookmarks-widget.tsx
+++ b/packages/widgets/src/bookmarks/bookmarks-widget.tsx
@@ -6,6 +6,7 @@ import {
Box,
Card,
Center,
+ Grid,
Group,
ScrollArea,
SimpleGrid,
@@ -156,6 +157,14 @@ export default function BookmarksWidget({
{cards}
+ ) : options.grow ? (
+
+ {cards.map((card) => (
+
+ {card}
+
+ ))}
+
) : (
{cards}
diff --git a/packages/widgets/src/bookmarks/index.tsx b/packages/widgets/src/bookmarks/index.tsx
index 82d171e9d..1fefa08c1 100644
--- a/packages/widgets/src/bookmarks/index.tsx
+++ b/packages/widgets/src/bookmarks/index.tsx
@@ -49,6 +49,7 @@ export const { definition, componentLoader } = createWidgetDefinition("bookmarks
hideHostname: factory.switch({ defaultValue: false }),
openNewTab: factory.switch({ defaultValue: true }),
withBorder: factory.switch({ defaultValue: false }),
+ grow: factory.switch({ defaultValue: false }),
customUrls: factory.internal({ defaultValue: [] as string[] }),
items: factory.sortableItemList({
ItemComponent: ({ item, handle, removeItem, removeLabel, rootAttributes }) => {
diff --git a/packages/widgets/src/bookmarks/layout.ts b/packages/widgets/src/bookmarks/layout.ts
index ada81edef..0c0d7b32e 100644
--- a/packages/widgets/src/bookmarks/layout.ts
+++ b/packages/widgets/src/bookmarks/layout.ts
@@ -240,8 +240,20 @@ export const getBookmarkDisplayPlan = ({
};
}
- let columns: number = widthSettings.compactColumns;
- let orientation: BookmarkOrientation = "horizontal";
+ if (layout === "gridHorizontal") {
+ return {
+ columns: Math.min(count, widthSettings.compactColumns),
+ horizontalScroll: false,
+ itemHeight: 56,
+ itemWidth: widthSettings.rowItemWidth,
+ orientation: "horizontal",
+ showHostname: widthSettings.showHostname && heightSettings.showHostname,
+ showTitle: widthSettings.showTitle && heightSettings.showTitle,
+ };
+ }
+
+ let columns: number = widthSettings.adaptiveColumns;
+ let orientation: BookmarkOrientation = widthSettings.adaptiveOrientation;
if (layout === "grid") {
columns = widthSettings.gridColumns;
orientation = "vertical";