guyu298 downloadsA start page with user-defined cards. Card content lives in a single dashboard note, split by headings, with per-card CSS snippets.
中文 · English
Turn one Markdown note into a start page. CardHomeTab splits that note by headings, renders every section as a card through Obsidian's own renderer, and lets each card carry its own CSS.
The plugin interface (commands, settings, notices) is currently available in Chinese only.
Enter opens the current tab, Ctrl/Cmd+Enter a new tab.MarkdownRenderer: plain text, fenced code, base blocks, and dataview/dataviewjs blocks when Dataview is installed. The core query embedded-search block is the exception — inside a card it shows up as a plain code block with the query source rather than search results (see the limitations).![[table.base]], ![[image.png]] and relative links resolve against the dashboard note's folder.%%card: %% comment: CSS snippets, column span, and icon.In Obsidian, open Settings → Community plugins → Browse and search for "CardHomeTab". This works once the plugin has been listed in the community directory.
main.js, manifest.json and styles.css from the latest release — or run npm ci && npm run build and take them from dist/.<Vault>/.obsidian/plugins/card-home-tab/ in your vault.dist/ is a self-contained build output: every build cleans it and regenerates it, so its contents can be installed or published as-is.
The default dashboard file is Home.md. A heading whose level equals the card heading level (2 by default) starts a card; the card body runs until the next heading of that level or a shallower one.
When the file does not exist: opening a new tab creates it with default content — two cards, one column each, each holding an inline base block in the cards view (one card per file, cardSize at the maximum of 800 so the cards render wide): recently modified (file.mtime, descending) and recently created (file.ctime, descending), 10 rows each, with the dashboard note itself excluded. Opening the home view by hand still stops at the "dashboard file not found" notice and waits for you to click "create and open" — on that path the usual cause is a mistyped path in the settings, and silently writing a file there would be worse.
The second card was meant to be "files you bookmarked", which is not possible: Bases has no bookmark property and no bookmark function, and bookmarks live in .obsidian/bookmarks.json, which Bases cannot read.
# My home
## Stats
%%card: css=stats; span=2; icon=lucide-chart%%
- Files in the vault: `1,204`
## To-do
%%card: css=user:todo%%
- [ ] Review the release notes
### This H3 stays inside the card
## Another card starts here
%%card: line is a plain card: span=1, no snippets, no icon.%%card: commentThe metadata line must be the first non-empty line after the heading.
%%card: css=mine; span=2; icon=lucide-chart%%
| Key | Value | Meaning |
|---|---|---|
css |
comma-separated snippet references | CSS snippets applied to this card only |
span |
integer ≥ 1 | how many grid columns the card spans |
col |
integer ≥ 1 | which column the card lives in; without it, columns are handed out round-robin in note order |
icon |
an Obsidian icon id, e.g. lucide-chart |
icon shown before the card title |
Syntax rules:
;, multiple values by ,, key and value by =.user: prefix mean the same thing: css=mine is css=user:mine.col beyond the current column count is clamped to the last column; a col and span that together overflow fall back to a position that fits, so they never create an extra column.%% %% is Obsidian's native comment syntax. It is hidden in reading mode and shown as dimmed text in Live Preview — that is Obsidian's behaviour, not extra markup added by the plugin.Cards are laid out by column: every card belongs to a column, cards stack top-down inside it and pull up tight, and columns do not affect one another. Dragging one card therefore never reshuffles the rest.
col key of %%card:. Cards without a col get columns round-robin in note order (1st → column 1, 2nd → column 2, …).col yet. This happens once, it records what was already on screen, so the layout does not shift. Without it the round-robin fallback depends on the note position, and moving a card would shift every later card's position and make them jump columns.span greater than 1 spans columns. It lands below the lowest free position of the columns it covers and pushes all of them down — a wide card couples the columns it spans at that point, which is the only sensible meaning of spanning. For drop targeting it counts as a member of every column it covers, so a card can be dropped above a wide card even when that wide card sits at the top.col is ignored. That threshold looks at the width the pane offers, not at the capped board width, so setting a cap below 900 does not collapse the columns.These are set once in the settings and apply to every card, so the common cases need no CSS at all. They are independent of one another, combine freely, and every default equals what the plugin looked like before, so upgrading changes nothing. The rules ship in the plugin's styles.css and are driven by a class or a CSS variable on the home view, so going back to the defaults leaves no trace.
The settings page groups them into collapsible blocks per type. Every block — including Branding, Background and Search — has a reset button on the right of its header. It only shows up when that block differs from the defaults, and it restores just that block.
| Type | Setting | What it does |
|---|---|---|
| Card | Show the card title | With it off, the title and icon are hidden and the content starts at the very top. The drag handle and the settings, edit and delete buttons float to the top-right corner and appear on hover — they live in the same DOM layer as the title, so they are moved rather than hidden with it. |
| Card | Corner radius | 0–24, defaults to 8 (the same as Obsidian's --radius-m). |
| Card | Card gap | 0–48, defaults to 16. Applies to both directions. |
| Card | Border style | None / solid / dashed / dotted, defaults to solid. Picking none is the same as setting the width to 0. |
| Card | Border width | 0–8, defaults to 1. |
| Card | Shadow | Never / always / on hover, defaults to never. The strength comes from the theme's --shadow-s. |
| Table | Zebra stripes | Gives the data rows of a Markdown table alternating backgrounds. The header is untouched, hover still works, and tables rendered by base or Dataview are left alone. |
| Base | Bar rows | Paints every row of a base table view as a full-width rounded block, with gaps instead of row and column separators. Cells stay side by side on one line: the Bases table is virtualised and JS positions its rows and cells, so this option only paints and never changes layout. |
| Base | Hide the toolbar | Hides the control row at the top (view switcher, result count, sort, filter, properties), leaving only the content. |
The colours can be overridden per theme or per vault: --home-tab-table-zebra for the stripes (which default to --background-modifier-hover), --home-tab-base-bar for the bar rows, and --home-tab-card-shadow for the card shadow.
More content types (base views, callouts) will follow the same pattern.
A reference is a bare file name or user:name — both read <config dir>/snippets/name.css, the same folder Obsidian's own appearance settings use.
Do not enable a card snippet in Settings → Appearance → CSS snippets: enabling it there applies it globally. The plugin reads the file itself and never touches enabledCssSnippets.
The snippet source is wrapped in
@scope (.home-card.home-card[data-card-id="…"]) {
/* your snippet, unchanged */
}
and attached to the document as a constructable stylesheet, so its rules can only match inside that one card. The scope class is repeated to lift specificity to 0,2,0, so theme selectors do not win over your snippet.
:root is rewritten to :scope. Inside @scope, :root still means the document root and would never match. Theme-prefixed selectors such as .theme-dark .foo keep working, because the theme class sits on body.
Snippets may not use @import. Such a snippet is rejected, a warning is logged, and the card renders without it; the other snippets on that card still apply.
Styles match the rendered DOM. Inside the scope:
.home-card[data-card-id]
├── .home-card-header icon, title, hover action bar
└── .home-card-content.markdown-rendered
└── .home-card-content-inner
└── rendered Markdown
markdown-rendered is added to .home-card-content itself, so .home-card-content.markdown-rendered p { … } is the form that matches.
Hover a card to reveal its action bar:
| Action | Behaviour |
|---|---|
| Drag handle | Moves the card to a position within a column. While dragging, a horizontal line hugs the card it will land under |
| Card settings | Dialog for icon, CSS snippets (built-in and user, multi-select) and column span; writes back to the %%card:%% line and re-renders that card |
| Edit | Opens the dashboard note with the cursor on that card's heading |
| Delete | Removes the section, after a confirmation |
Cards are rendered read-only on the home view; all writing happens in the note.
Commands (the interface is in Chinese):
| Command | Meaning |
|---|---|
打开首页 |
Open the home view |
刷新所有卡片 |
Re-render every card |
在标签页打开仪表盘 |
Open the dashboard note in a tab |
新建卡片 |
Append a new card section to the dashboard note |
| Section | Settings |
|---|---|
| Page | Dashboard file (a text field with note completion, so a path that does not exist yet can be typed in), card heading level (H2–H6), grid columns (1–6, defaults to 2), limit the board width and the board width itself (600–2400, only shown while the limit is on), replace new tabs, open home on startup |
| Branding | Logo type (none / built-in icon / vault image / URL), icon id or image path, logo scale (0.2–5), logo colour (empty follows the theme accent; built-in icons only), wordmark text, show wordmark, wordmark font size, wordmark font weight (100–900) |
| Background | Background type (none / vault image / URL), light background, dark background (empty falls back to the light one), blur (0–40), dim (0–100) |
| Search | Show search bar, Markdown only, show path, show bookmarks, show recent files, max results (1–50), recent files count (0–20) |
| Content styles | Collapsible blocks per type: card (show title, radius, gap, border style and width, shadow), table (zebra stripes), base (bar rows, hide the toolbar) |
| Snippets | Your snippets with their paths, the snippet folder, and a "refresh list" button |
updateCardMeta and appendCard always insert \n, so editing a CRLF dashboard note leaves the touched line with \n while the rest of the file keeps \r\n. Parsing is unaffected; the file just stops being uniformly CRLF on the line the plugin wrote.display()), which minAppVersion 1.9.0 requires, and getSettingDefinitions() returns an empty array, so 1.13+ shows the page but its settings are not searchable.%%card: value cannot contain ; (the field separator), and a token without = is not representable. Rather than silently dropping what it cannot represent, the plugin refuses to rewrite such a line and tells you to fix it by hand.@import. Such a snippet is rejected and logged, and the card renders without it.css=auto is read as a snippet named auto, and a leftover css=builtin:x does not resolve at all — both are simply ignored.@scope needs Chromium 118+, and base blocks were introduced in 1.9.0, so minAppVersion is 1.9.0. All features use public APIs, which is why the plugin loads on mobile and the cards render there.span does not divide the column count. With three columns and every card at span=2, the cards can only occupy columns 1-2 one after another and nothing ever reaches column 3. That is inherent to spanning (a second span-2 card does not fit in three columns), not a layout bug; such a dashboard wants a column count of 2.base views and Dataview render, because those bring their own row markup and theme styling.%%card: %% line is visible as dimmed text in Live Preview.:hover/:focus-within on the card, and its controls are non-focusable spans. A touch device has no hover and there is no keyboard route to them, so settings, edit, delete and drag are desktop-only. The cards themselves render on mobile..md is refused: the home view reports the file as missing, and creating it fails with a notice. It cannot be used to point the plugin at, for instance, a .canvas.新建卡片 and 在标签页打开仪表盘 create the note at whatever path the setting currently holds, without asking; 新建卡片 then appends a card to it. A typo in the path leaves a stray note behind instead of failing.query embedded-search block produces no results. Obsidian only turns a query block into an embedded search under a reading-mode MarkdownView host. The plugin renders through the static MarkdownRenderer.render, which has no such host, so a query block falls back to a plain code block showing the query source instead of search results. dataview and base blocks are unaffected (they are properly registered code-block processors). Use a dataview or base block for a dynamic list.