+ {/* Title (24px) + export menu + "Ny leverantör" action (pills) */}
+
+
+ {/* Search toolbar */}
+
+
+ {/* Borderless table: header row + single-line rows */}
+
+
+
+
+
+ {[1, 2, 3, 4, 5, 6].map((i) => (
+
+ ))}
+
+
+ )
+}
diff --git a/components/common/DetailPageSkeleton.tsx b/components/common/DetailPageSkeleton.tsx
new file mode 100644
index 00000000..541043cc
--- /dev/null
+++ b/components/common/DetailPageSkeleton.tsx
@@ -0,0 +1,86 @@
+import { Skeleton } from '@/components/ui/skeleton'
+import { cn } from '@/lib/utils'
+
+interface DetailPageSkeletonProps {
+ /** Number of summary cards under the title row. */
+ cards?: 2 | 3
+ /** Full-width variant for MainContainer's wide routes (max-w-7xl). */
+ wide?: boolean
+ className?: string
+}
+
+/**
+ * Silhouette of a register/document detail page: back link, title row with
+ * action pills, then a card grid. Used both as the route-level loading.tsx of
+ * the [id] segments and as the client page's own loading state, so the
+ * handoff from the RSC fallback to the client fetch is a no-op visually
+ * instead of skeleton -> centred spinner -> content (three unrelated layouts
+ * on the most-travelled drill-down path).
+ */
+export function DetailPageSkeleton({ cards = 2, wide = false, className }: DetailPageSkeletonProps) {
+ return (
+
+ {/* Back link */}
+
+
+ {/* Title (24px) + status pill + primary actions (pills) */}
+
+
+ {/* Card grid */}
+
+ {Array.from({ length: cards }, (_, i) => (
+
+
+
+
+
+
+ ))}
+
+
+ {/* Line/table block */}
+
+
+
+
+
+ {[1, 2, 3].map((i) => (
+
+
+
+
+ ))}
+
+
+ )
+}
+
+/**
+ * Silhouette of the invoice editor (single-column snabbflöde): heading, the
+ * customer/details block and the lines block. Matches the fallback the
+ * "Ny faktura" dialog shows while the editor chunk loads.
+ */
+export function InvoiceEditorSkeleton({ className }: { className?: string }) {
+ return (
+