Files
site-portfolio/public/en/cases/card-columns-obsidian/index.html
T

716 lines
37 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en" data-theme="dark" data-radius="medium" data-font="merriweather">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<meta
name="description"
content="How I built Card Columns for Obsidian Bases: group notes by properties, filter tags with AND/OR, and move cards between columns."
/>
<title>Card Columns for Obsidian Bases: Kanban view case study</title>
<script>
(function () {
var FONT_CSS = {
merriweather: "/assets/fonts/merriweather.css",
geologica: "/assets/fonts/geologica.css",
mono: "/assets/fonts/mono.css",
};
try {
var t = JSON.parse(localStorage.getItem("siteThemeV1") || "{}");
var d = document.documentElement;
var m = t.mode;
if (m !== "light" && m !== "dark" && m !== "system") m = "system";
var eff =
m === "system"
? window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light"
: m;
d.dataset.theme = eff;
var h = typeof t.h === "number" ? t.h : typeof t.hue === "number" ? t.hue : null;
var s = typeof t.s === "number" ? t.s : typeof t.sat === "number" ? t.sat : null;
var l = typeof t.l === "number" ? t.l : typeof t.light === "number" ? t.light : null;
if (h !== null) d.style.setProperty("--theme-h", String(h));
if (s !== null) d.style.setProperty("--theme-s", String(s));
if (l !== null) {
var L = Number(l);
if (Number.isFinite(L)) d.style.setProperty("--theme-l", String(Math.min(70, Math.max(20, L))));
}
var hVal = h !== null ? h : 210;
var sVal = s !== null ? s : 90;
var lVal = l !== null ? Math.min(70, Math.max(20, Number(l))) : 40;
var H = (((hVal % 360) + 360) % 360) / 360;
var S = Math.min(100, Math.max(0, sVal)) / 100;
var Lv = Math.min(100, Math.max(0, lVal)) / 100;
var r, g, b;
if (S === 0) { r = g = b = Lv; }
else {
var q = Lv < 0.5 ? Lv * (1 + S) : Lv + S - Lv * S;
var p = 2 * Lv - q;
var hue2rgb = function(p, q, t) {
if (t < 0) t += 1; if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
r = hue2rgb(p, q, H + 1/3);
g = hue2rgb(p, q, H);
b = hue2rgb(p, q, H - 1/3);
}
var lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;
d.style.setProperty("--on-accent", lum > 0.58 ? "hsl(220 14% 12%)" : "hsl(0 0% 98%)");
var rad = t.radius;
if (rad === "soft" || rad === "medium" || rad === "strong") {
d.dataset.radius = rad;
} else {
d.dataset.radius = "medium";
}
var f = t.font;
if (f === "onest") f = "geologica";
if (f === "anonymous") f = "mono";
if (f !== "merriweather" && f !== "mono" && f !== "geologica") f = "merriweather";
d.dataset.font = f;
var mb = t.meshBg;
if (mb === "off") {
d.dataset.meshBg = "off";
} else {
d.dataset.meshBg = "on";
}
var href = FONT_CSS[f];
if (href && !document.querySelector('link[data-theme-font="' + f + '"]')) {
var link = document.createElement("link");
link.rel = "stylesheet";
link.href = href;
link.setAttribute("data-theme-font", f);
document.head.appendChild(link);
}
} catch (e) {}
})();
</script>
<link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon/favicon-16x16.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/assets/favicon/apple-touch-icon.png" />
<link rel="manifest" href="/assets/favicon/site.webmanifest" />
<link rel="preload" href="/assets/fonts/merriweather.css" as="style" />
<link rel="preload" href="/css/main.css" as="style" />
<link rel="preload" href="/js/skill-connectors.js" as="script" />
<link rel="modulepreload" href="/js/main.js" />
<link rel="modulepreload" href="/js/modules/ui/page-mesh-shader.js" />
<link rel="stylesheet" href="/css/main.css" />
<link rel="canonical" href="https://mamadaevv.site/en/cases/card-columns-obsidian/" />
<link rel="alternate" hreflang="ru" href="https://mamadaevv.site/cases/card-columns-obsidian/" />
<link rel="alternate" hreflang="en" href="https://mamadaevv.site/en/cases/card-columns-obsidian/" />
<link rel="alternate" hreflang="x-default" href="https://mamadaevv.site/cases/card-columns-obsidian/" />
<meta name="author" content="Nikolai Mamadaev" />
<meta name="robots" content="index, follow" />
<meta name="theme-color" content="#0f1115" />
<!-- Open Graph -->
<meta property="og:type" content="article" />
<meta property="og:site_name" content="Nikolai Mamadaev — Product Designer" />
<meta property="og:locale" content="en_US" />
<meta property="og:locale:alternate" content="ru_RU" />
<meta property="og:title" content="Card Columns for Obsidian Bases: Kanban view case study" />
<meta property="og:description" content="How I built Card Columns for Obsidian Bases: group notes by properties, filter tags with AND/OR, and move cards between columns." />
<meta property="og:url" content="https://mamadaevv.site/en/cases/card-columns-obsidian/" />
<meta property="og:image" content="https://mamadaevv.site/assets/og/og-default.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content="Card Columns for Obsidian Bases: Kanban view case study" />
<!-- Twitter / X -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Card Columns for Obsidian Bases: Kanban view case study" />
<meta name="twitter:description" content="How I built Card Columns for Obsidian Bases: group notes by properties, filter tags with AND/OR, and move cards between columns." />
<meta name="twitter:image" content="https://mamadaevv.site/assets/og/og-default.png" />
<meta name="twitter:image:alt" content="Card Columns for Obsidian Bases: Kanban view case study" />
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Person",
"@id": "https://mamadaevv.site/#person",
"name": "Nikolai Mamadaev",
"url": "https://mamadaevv.site",
"jobTitle": "Product Designer",
"description": "Product designer with 10+ years of experience. Designing digital products, interfaces and design systems.",
"knowsAbout": [
"UX/UI design",
"product design",
"design systems",
"prototyping",
"Figma",
"no-code"
],
"sameAs": [
"https://t.me/mamadaevv",
"https://vk.com/mamadaevv",
"https://github.com/mamadaevv"
]
},
{
"@type": "WebSite",
"@id": "https://mamadaevv.site/#website",
"url": "https://mamadaevv.site/",
"name": "Nikolai Mamadaev — Product Designer",
"inLanguage": "en",
"author": {
"@id": "https://mamadaevv.site/#person"
},
"publisher": {
"@id": "https://mamadaevv.site/#person"
}
},
{
"@type": "CreativeWork",
"@id": "https://mamadaevv.site/en/cases/card-columns-obsidian/#page",
"url": "https://mamadaevv.site/en/cases/card-columns-obsidian/",
"name": "Card Columns for Obsidian Bases: Kanban view case study",
"description": "How I built Card Columns for Obsidian Bases: group notes by properties, filter tags with AND/OR, and move cards between columns.",
"isPartOf": {
"@id": "https://mamadaevv.site/#website"
},
"author": {
"@id": "https://mamadaevv.site/#person"
},
"inLanguage": "en",
"creator": {
"@id": "https://mamadaevv.site/#person"
},
"datePublished": "2026-09-29",
"about": "UX/UI design and interface design"
},
{
"@type": "BreadcrumbList",
"@id": "https://mamadaevv.site/en/cases/card-columns-obsidian/#breadcrumbs",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Nikolai Mamadaev — Product Designer",
"item": "https://mamadaevv.site/"
},
{
"@type": "ListItem",
"position": 2,
"name": "Cases",
"item": "https://mamadaevv.site/cases/"
},
{
"@type": "ListItem",
"position": 3,
"name": "Card Columns for Obsidian Bases: Kanban view case study",
"item": "https://mamadaevv.site/cases/card-columns-obsidian/"
}
]
}
]
}
</script>
<script src="/js/skill-connectors.js" defer></script>
<script type="module" src="/js/main.js"></script>
<link rel="stylesheet" href="/vendor/plyr/plyr.css" />
<style>
.case-article {
max-width: 48rem;
margin: 0 auto;
}
.case-article h2 {
margin: 2rem 0 0.75rem;
font-size: clamp(1.15rem, 2vw, 1.35rem);
font-weight: 700;
color: var(--text);
}
.case-article h3 {
font-size: 1.05rem;
font-weight: 600;
color: var(--text);
}
.case-article .section__prose {
margin-bottom: 0.75rem;
color: var(--text);
}
.case-article img,
.case-article video {
display: block;
max-width: 100%;
height: auto;
margin: 0.75rem 0 1rem;
}
.case-article ul,
.case-article ol {
margin: 0.5rem 0 1rem 1.25rem;
padding: 0;
}
.case-article li {
margin-bottom: 0.35rem;
}
.case-article a:not(.btn) {
color: var(--accent);
}
.case-block {
margin-top: 3rem;
padding-top: 2.5rem;
border-top: 1px solid color-mix(in srgb, var(--text) 14%, transparent);
}
.case-block#cards,
.case-block#rezultat {
border-top: 0;
}
.case-block:first-of-type {
margin-top: 0;
padding-top: 0;
border-top: 0;
}
</style>
<script>
(function () {
try {
var t = JSON.parse(localStorage.getItem("siteThemeV1") || "{}");
var d = document.documentElement;
var m = t.mode;
if (m !== "light" && m !== "dark" && m !== "system") m = "system";
var eff =
m === "system"
? window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light"
: m;
d.dataset.theme = eff;
var h = typeof t.h === "number" ? t.h : typeof t.hue === "number" ? t.hue : null;
var s = typeof t.s === "number" ? t.s : typeof t.sat === "number" ? t.sat : null;
var l = typeof t.l === "number" ? t.l : typeof t.light === "number" ? t.light : null;
if (h !== null) d.style.setProperty("--theme-h", String(h));
if (s !== null) d.style.setProperty("--theme-s", String(s));
if (l !== null) {
var L = Number(l);
if (Number.isFinite(L)) d.style.setProperty("--theme-l", String(Math.min(70, Math.max(20, L))));
}
var rad = t.radius;
if (rad === "soft" || rad === "medium" || rad === "strong") {
d.dataset.radius = rad;
} else {
d.dataset.radius = "medium";
}
var f = t.font;
if (f === "onest") f = "geologica";
if (f === "anonymous") f = "mono";
if (f === "merriweather" || f === "mono" || f === "geologica") {
d.dataset.font = f;
} else {
d.dataset.font = "merriweather";
}
var mb = t.meshBg;
if (mb === "off") {
d.dataset.meshBg = "off";
} else {
d.dataset.meshBg = "on";
}
} catch (e) {}
})();
</script>
</head>
<body data-current-case="card-columns">
<svg class="liquid-glass-defs" aria-hidden="true" width="0" height="0" focusable="false" style="position:absolute;width:0;height:0;overflow:hidden;pointer-events:none">
<filter id="liquid-glass-displacement" x="-20%" y="-20%" width="140%" height="140%" color-interpolation-filters="sRGB">
<feImage href="../../../assets/liquid-glass/displacement.png" xlink:href="../../../assets/liquid-glass/displacement.png" preserveAspectRatio="none" result="lgMap"/>
<feDisplacementMap in="SourceGraphic" in2="lgMap" scale="14" xChannelSelector="R" yChannelSelector="G"/>
</filter>
<filter id="liquid-glass-displacement-btn" x="-20%" y="-20%" width="140%" height="140%" color-interpolation-filters="sRGB">
<feImage href="../../../assets/liquid-glass/displacement.png" xlink:href="../../../assets/liquid-glass/displacement.png" preserveAspectRatio="none" result="lgMapBtn"/>
<feDisplacementMap in="SourceGraphic" in2="lgMapBtn" scale="6" xChannelSelector="R" yChannelSelector="G"/>
</filter>
</svg>
<a class="skip-link" href="#case-main">Skip to case content</a>
<header class="header" data-theme-picker>
<div class="header__inner">
<a class="logo" href="../../" aria-label="Home">NM</a>
<div class="header__cluster">
<nav class="nav" aria-label="Page sections">
<a href="#contact">Contact</a>
</nav>
</div>
</div>
</header>
<div class="fab-stack" aria-label="Quick actions">
<div class="theme-panel-fab-wrap">
<button type="button" class="fab fab--secondary glass-neutral" data-theme-panel-btn aria-expanded="false" aria-controls="theme-panel" aria-label="Style settings">
<span class="theme-panel-fab__icon theme-panel-fab__icon--open-hide" aria-hidden="true"></span>
<svg class="theme-panel-fab__icon theme-panel-fab__icon--close" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M6 6L18 18M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
</svg>
</button>
<div id="theme-panel" class="theme-picker__panel glass-neutral" role="dialog" aria-label="Theme &amp; appearance" hidden data-theme-panel>
<div class="theme-picker__stack">
<section class="theme-picker__section" aria-labelledby="theme-accent-title">
<h3 id="theme-accent-title" class="theme-picker__section-title">Accent color</h3>
<div class="colorpicker" data-colorpicker>
<div class="colorpicker__plane-box">
<canvas class="colorpicker__plane" width="320" height="180" data-sl-canvas aria-label="Accent saturation and lightness"></canvas>
<div class="colorpicker__knob" data-sl-knob></div>
</div>
<div class="colorpicker__hue-wrap">
<label class="colorpicker__hue-sr" for="theme-hue-slider">Accent hue</label>
<input id="theme-hue-slider" class="colorpicker__hue" type="range" min="0" max="360" value="210" data-hue-slider aria-valuemin="0" aria-valuemax="360" />
</div>
</div>
</section>
<section class="theme-picker__section" aria-labelledby="theme-mode-title">
<h3 id="theme-mode-title" class="theme-picker__section-title">Theme</h3>
<div class="radius-seg theme-mode-seg" role="radiogroup" aria-labelledby="theme-mode-title">
<button type="button" class="radius-seg__btn" role="radio" aria-checked="false" data-theme-mode="system" aria-label="System"><span class="radius-seg__viz" aria-hidden="true"><span class="theme-mode-seg__ico theme-mode-seg__ico--system"></span></span></button>
<button type="button" class="radius-seg__btn" role="radio" aria-checked="false" data-theme-mode="light" aria-label="Light"><span class="radius-seg__viz" aria-hidden="true"><span class="theme-mode-seg__ico theme-mode-seg__ico--light"></span></span></button>
<button type="button" class="radius-seg__btn" role="radio" aria-checked="false" data-theme-mode="dark" aria-label="Dark"><span class="radius-seg__viz" aria-hidden="true"><span class="theme-mode-seg__ico theme-mode-seg__ico--dark"></span></span></button>
</div>
</section>
<section class="theme-picker__section" aria-labelledby="theme-radius-title">
<h3 id="theme-radius-title" class="theme-picker__section-title">Corner radius</h3>
<div class="radius-seg" role="radiogroup" aria-labelledby="theme-radius-title">
<button type="button" class="radius-seg__btn" role="radio" aria-checked="false" data-radius-option="soft" aria-label="Soft radius"><span class="radius-seg__viz" aria-hidden="true"><span class="radius-seg__arc radius-seg__arc--soft"></span></span></button>
<button type="button" class="radius-seg__btn" role="radio" aria-checked="false" data-radius-option="medium" aria-label="Medium radius"><span class="radius-seg__viz" aria-hidden="true"><span class="radius-seg__arc radius-seg__arc--medium"></span></span></button>
<button type="button" class="radius-seg__btn" role="radio" aria-checked="false" data-radius-option="strong" aria-label="Strong radius"><span class="radius-seg__viz" aria-hidden="true"><span class="radius-seg__arc radius-seg__arc--strong"></span></span></button>
</div>
</section>
<section class="theme-picker__section" aria-labelledby="theme-typography-title">
<h3 id="theme-typography-title" class="theme-picker__section-title">Typography</h3>
<div class="radius-seg" role="radiogroup" aria-labelledby="theme-typography-title">
<button type="button" class="radius-seg__btn theme-font-seg__btn" role="radio" aria-checked="false" data-font-option="geologica" aria-label="Sans"><span class="theme-font-seg__label">Sans</span></button>
<button type="button" class="radius-seg__btn theme-font-seg__btn" role="radio" aria-checked="false" data-font-option="merriweather" aria-label="Sans Serif"><span class="theme-font-seg__label">Sans Serif</span></button>
<button type="button" class="radius-seg__btn theme-font-seg__btn" role="radio" aria-checked="false" data-font-option="mono" aria-label="Mono"><span class="theme-font-seg__label">Mono</span></button>
</div>
</section>
<section class="theme-picker__section" aria-labelledby="theme-mesh-bg-title">
<h3 id="theme-mesh-bg-title" class="theme-picker__section-title">Page background</h3>
<div class="radius-seg radius-seg--two" role="radiogroup" aria-labelledby="theme-mesh-bg-title">
<button type="button" class="radius-seg__btn theme-mesh-bg-seg__btn" role="radio" aria-checked="false" data-mesh-bg-option="on" aria-label="Show background gradient"><span class="theme-mesh-bg-seg__label">On</span></button>
<button type="button" class="radius-seg__btn theme-mesh-bg-seg__btn" role="radio" aria-checked="false" data-mesh-bg-option="off" aria-label="Hide background gradient"><span class="theme-mesh-bg-seg__label">Off</span></button>
</div>
</section>
</div>
</div>
</div>
<a class="fab fab--primary" href="#contact" aria-label="Contact">
<span class="fab-primary__ico" aria-hidden="true"></span>
</a>
</div>
<div class="content-edit-fab-stack" aria-label="Content Edits" data-content-edit-ui>
<button
type="button"
class="fab fab--secondary glass-neutral content-edit-fab"
data-content-edit-mode-toggle
hidden
aria-pressed="false"
aria-label="Content Editing Mode"
title="Edit Mode"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path
d="M4 20h4l10.5-10.5a2.12 2.12 0 0 0 0-3L16.5 4.5a2.12 2.12 0 0 0-3 0L3 15v5z"
stroke="currentColor"
stroke-width="1.75"
stroke-linejoin="round"
/>
</svg>
<span class="content-edit-fab__badge" hidden aria-hidden="true">0</span>
</button>
</div>
<main id="case-main" class="section section--tight">
<div class="section__inner case-article">
<p class="hero__eyebrow">Obsidian Bases · Plugin</p>
<h1 class="case-page__h1">
A Kanban board for Obsidian Bases<br />
with filtering and drag-and-drop
</h1>
<p class="case-page__sub">
Card Columns: group by properties,<br />
filter tags with AND/OR
</p>
<figure class="case-media case-media--wide">
<img src="../../../assets/card-columns-obsidian/overview.webp" alt="An Obsidian links database grouped into Card Columns by tags" width="2076" height="1288" loading="eager" decoding="async" data-image-viewer />
<figcaption>Links: 196 results in Columns view</figcaption>
</figure>
<p class="section__prose">
I built Card Columns as an additional card view for Obsidian Bases. The built-in Cards view showed
notes in a grid, but I needed columns. I couldn't find a ready-made plugin for the scenario I wanted,
so I built my own. The plugin is listed in the
<a href="https://community.obsidian.md/plugins/card-columns" target="_blank" rel="noopener noreferrer">Obsidian Community directory</a>
and is open source under the MIT license.
</p>
<div class="case-block" id="kontekst">
<h2>Another card view for Bases</h2>
<p class="section__prose">
In Obsidian Bases, I configure a query in the UI and choose how to display its results: as a table,
list, or cards. Card Columns adds another card view, grouping notes into columns by a selected property.
</p>
<p class="section__prose">
I set sorting, filters, search, and visible properties in the native Bases toolbar. Columns uses that
result and changes only its presentation. Browsing does not change notes. Dragging a card updates
the property used to group the columns.
</p>
</div>
<div class="case-block" id="filters">
<h2>Group first, filter second</h2>
<p class="section__prose">
In the Bases toolbar, I choose Group by and select a tag, status, category, or another property.
With no group selected, Columns groups by tags. Changing Group by reshapes the columns.
</p>
<aside class="case-callout" data-callout>
<span class="case-callout__emoji case-callout__emoji--pulse" aria-hidden="true">💡</span>
<div class="case-callout__content">
<p>
This was the problem that started Columns: I needed the same card in several columns. A link tagged
“Tools” and “AI” appears in both. In a movie database, one film can belong to both “Comedy” and
“Family.” I couldn't find this behavior in the ready-made plugins I tried, so I built Columns.
The note stays single and appears in every matching group.
</p>
</div>
</aside>
<p class="section__prose">
Tag pills sit under the toolbar, beside the board. A regular click filters to that tag; clicking
another adds it in OR mode. Ctrl-click or right-click toggles one tag without clearing the rest.
OR/AND switches between matching any selected tag and matching all of them. All clears the filter.
</p>
<p class="section__prose">
Tag pills refine the current results. The native Filter sets the main conditions in the toolbar,
while the pills quickly narrow the view without taking me away from the board.
</p>
<figure class="case-media case-media--wide">
<div class="case-video-shell">
<video controls preload="metadata" playsinline poster="../../../assets/card-columns-obsidian/filter-use.webp" aria-label="Tag filtering demo in Card Columns" data-case-video-player style="display: block; width: 100%; height: auto; margin: 0">
<source src="../../../assets/card-columns-obsidian/filter-use.webm" type="video/webm" />
Your browser does not support video.
</video>
</div>
<figcaption>Tag filtering: OR and AND</figcaption>
</figure>
</div>
<div class="case-block" id="cards">
<h2>Cards show the fields I need</h2>
<p class="section__prose">
I choose visible fields from the native Properties list. The first selected property becomes the
title; the rest appear as chips: text, tags, links, booleans, and dates. Properties chips can be
stacked vertically or arranged in a compact grid. I can also set wrapping for multi-line values and
date formats. The cards show the fields selected for each database without changing the notes themselves.
</p>
<p class="section__prose">
For a cover, I can use the first image in a note or a frontmatter property value. The settings control
its aspect ratio (Auto, 1:1, 3:2, 4:3, or 16:9), fit, and position. I can open a card in the
active pane, a modal, a new tab, or a split. In a split, a click reuses the pane; Ctrl-click opens
another tab in that pane. The board stays in view, so I can follow the current query.
</p>
<h3>Designed to fit Obsidian</h3>
<p class="section__prose">
I designed and built the interface directly in code, without Figma or another design editor.
I followed Obsidian’s design system so Columns feels like part of the app.
</p>
</div>
<div class="case-block" id="reshenie">
<h2>Dragging a card edits its note</h2>
<p class="section__prose">
Version 0.9.0 supports moving cards between columns. When a card moves, Columns updates the
grouping property through Obsidian’s <code>processFrontMatter()</code> API. On a status board, that
changes the note’s status. If the property is a list, Columns replaces the moved value and leaves
the others intact.
</p>
<p class="section__prose">
Drag-and-drop is enabled by default. A switch in General settings turns it off, so the board will
not update frontmatter when cards move.
</p>
<h3>Settings grouped by type</h3>
<p class="section__prose">
General, Title, Properties, and Cover group settings by what they control, so the one I need is easier
to find. In General, I set card width, one to six columns per group, and column order. I can leave the
order automatic or set it manually; the manual layout keeps empty columns in place.&nbsp;A&nbsp;lane such
as Done stays visible even when it has no cards yet.
</p>
<p class="section__prose">
Grid layout places several cards side by side. Masonry fills vertical gaps, while zebra striping
makes a wide board easier to scan. Title style, property chips, dates, and covers have their own controls,
so the same view can fit different databases.
</p>
<figure class="case-media case-media--wide">
<div class="case-video-shell">
<video controls preload="metadata" playsinline poster="../../../assets/card-columns-obsidian/view-configure.webp" aria-label="Columns view configuration demo" data-case-video-player style="display: block; width: 100%; height: auto; margin: 0">
<source src="../../../assets/card-columns-obsidian/view-configure.webm" type="video/webm" />
Your browser does not support video.
</video>
</div>
<figcaption>Columns view settings</figcaption>
</figure>
</div>
<div class="case-block" id="publikaciya">
<h2>From development to release</h2>
<p class="section__prose">
I develop Card Columns with Hermes Agent. The source code lives on the agent’s VPS, and the built
plugin is synced to my laptop. I test it there in my Obsidian vault.
</p>
<p class="section__prose">
If it works as expected, the agent’s scripts send the changes to Gitea and GitHub. I initiate the
release of each new version in the Obsidian Community directory myself.
</p>
</div>
<div class="case-block" id="rezultat">
<h2>Result: a board inside Obsidian Bases</h2>
<p class="section__prose">
Card Columns keeps notes in Bases while changing how I browse them. I choose a grouping, filter by tags,
and open cards beside the board. When I need to change a status, moving a card updates the property used
to build the columns.
</p>
<p class="section__prose">
By the end of September 2026, Card Columns had 846 organic downloads in the Obsidian Community
directory. The listing showed version 0.9.0. On
<a href="https://github.com/mamadaevv/card-columns" target="_blank" rel="noopener noreferrer">GitHub</a>,
Card Columns had four stars and two issues. One bug report is closed; one request to choose a property
for covers is still open. Some requested improvements have already shipped. The plugin supports Obsidian 1.8.0+
on desktop and mobile under the MIT license.
</p>
<aside class="case-callout" data-callout>
<span class="case-callout__emoji case-callout__emoji--pulse" aria-hidden="true">✅</span>
<div class="case-callout__content">
<p>
The main result for me is a more useful way to present and scan cards. I choose the fields and
grouping, and one note can appear in every matching column. Drag-and-drop edits the property
separately and can be turned off.
</p>
</div>
</aside>
</div>
</div>
</main>
<section class="section section--surface" data-reveal>
<div class="section__inner">
<h2 class="section__title">See also</h2>
<ol class="mini-projects-grid" data-mini-projects-list>
<li class="mini-projects-grid__item" data-mp-cells="3" data-mp-cells-sm="2" data-related-slug="ai-assistant">
<a
class="mp-card mp-card--link mp-card--compact"
href="../ai-assistant-figma/"
aria-label="AI Assistant — Open a Case"
>
<div class="mp-card__body">
<h3 class="mp-card__title">AI Assistant</h3>
<ul class="mp-card__tags" aria-label="Project Type">
<li class="mp-card__tag">Figma plugin</li>
</ul>
<p class="mp-card__desc">A Figma plugin with AI features.</p>
</div>
</a>
</li>
<li class="mini-projects-grid__item" data-mp-cells="3" data-mp-cells-sm="2" data-related-slug="card-columns">
<a
class="mp-card mp-card--link mp-card--compact"
href="../card-columns-obsidian/"
aria-label="Card Columns — Open a Case"
>
<div class="mp-card__body">
<h3 class="mp-card__title">Card Columns</h3>
<ul class="mp-card__tags" aria-label="Project Type">
<li class="mp-card__tag">Obsidian Plugin</li>
</ul>
<p class="mp-card__desc">A plugin for displaying cards in columns in Obsidian.</p>
</div>
</a>
</li>
<li class="mini-projects-grid__item" data-mp-cells="3" data-mp-cells-sm="2" data-related-slug="site-portfolio">
<a
class="mp-card mp-card--link mp-card--compact"
href="../site-portfolio/"
aria-label="This website — Open a Case"
>
<div class="mp-card__body">
<h3 class="mp-card__title">This website</h3>
<ul class="mp-card__tags" aria-label="Project Type">
<li class="mp-card__tag">Development with AI IDE</li>
</ul>
<p class="mp-card__desc">Website development using AI IDE.</p>
</div>
</a>
</li>
</ol>
</div>
</section>
<section id="contact" class="section section--surface section--footerish" data-reveal>
<div class="section__inner">
<h2 class="section__title">Contact</h2>
<ul class="contact-grid">
<li><a class="contact-card contact-card--link contact-card--brand-tg" href="https://t.me/mamadaevv" rel="noopener noreferrer"><span class="contact-card__corner" aria-hidden="true"></span><span class="contact-card__icon" aria-hidden="true"><img src="../../../assets/shared/icons/tg.svg" alt="" width="24" height="24" loading="eager" decoding="async" /></span><span class="contact-card__body"><span class="contact-card__name">Telegram</span><span class="contact-card__hint">I'll reply today</span></span></a></li>
<li><a class="contact-card contact-card--link contact-card--brand-whatsapp" href="https://wa.me/qr/E2AWW5ZHE3ANJ1" rel="noopener noreferrer"><span class="contact-card__corner" aria-hidden="true"></span><span class="contact-card__icon" aria-hidden="true"><img src="../../../assets/shared/icons/whatsapp.svg" alt="" width="24" height="24" loading="eager" decoding="async" /></span><span class="contact-card__body"><span class="contact-card__name">WhatsApp</span><span class="contact-card__hint">Stable connection</span></span></a></li>
<li><button type="button" class="contact-card contact-card--action contact-card--brand-email" data-contact-copy data-email-b64="bmlrQG1hbWFkYWV2di5zaXRl" data-hint-default="Copy address" aria-label="Copy email address to clipboard"><span class="contact-card__corner contact-card__corner--copy" aria-hidden="true"></span><span class="contact-card__icon contact-card__icon--accent" aria-hidden="true"></span><span class="contact-card__body"><span class="contact-card__name">Email</span><span class="contact-card__hint" data-contact-copy-hint>Copy address</span></span></button></li>
</ul>
</div>
</section>
<footer class="footer">
<div class="footer__inner">
<span class="footer__chunk footer__chunk--date">© 2015–2026<span class="footer__sep" aria-hidden="true"> ·
</span></span>
<span class="footer__chunk footer__chunk--main">Website design and development — Nikolay Mamadaev</span> ·<span class="footer__chunk footer__chunk--meta footer__attr"><span class="footer__sep" aria-hidden="true"> </span><a href="../../cases/design-system/">Design System</a><span class="footer__sep" aria-hidden="true">·
</span><a href="../../cookie-policy/">Cookie policy</a></span> ·<span class="footer__chunk footer__chunk--lang"><span class="footer__sep" aria-hidden="true">
</span><nav class="lang-switch" aria-label="Choose language" data-lang-switch>
<a class="lang-switch__btn" data-lang="ru" href="/">Русский</a>
</nav>
<script>
(function () {
var p = location.pathname.replace(/^\/+/, "");
var isEn = p.indexOf("en/") === 0;
var base = isEn ? p.slice(3) : p;
var links = document.querySelectorAll("[data-lang-switch] [data-lang]");
for (var i = 0; i < links.length; i++) {
var l = links[i].getAttribute("data-lang");
links[i].setAttribute("href", (l === "en" ? "/en/" : "/") + base);
}
})();
</script>
</span></div>
</footer>
<aside class="cookie-notice glass-neutral" hidden data-cookie-notice>
<p class="cookie-notice__text">
This site uses cookies to ensure a smooth user experience. For more information, see the
<a href="../../cookie-policy/" data-cookie-policy-link>cookie policy</a>.
</p>
<button type="button" class="btn btn--secondary btn--flat cookie-notice__accept" data-cookie-accept>
<span>Accept</span>
<span class="cookie-notice__accept-icon" aria-hidden="true"><img src="../../../assets/shared/icons/сookie.svg" alt="" width="20" height="20" loading="eager" decoding="async" /></span>
</button>
</aside>
<div id="nav-more-menu-floating" class="nav-more__floating" hidden data-nav-more-menu-floating aria-hidden="true"></div>
<script>
document.addEventListener('DOMContentLoaded',function(){
var s=document.body.dataset.currentCase;
if(!s)return;
document.querySelectorAll('[data-related-slug="'+s+'"]').forEach(function(el){el.hidden=true;});
});
</script>
</body>
</html>