Final and Working Edition of My Progression-Based To-Do Setup in Bases
Alright, so here’s the final and working version of my Bases setup.
Before I get into why it works, I’ll explain what exactly I was trying to build.
The whole thing is meant to be a progression-based to-do list, where each item has three Boolean checkpoints (1, 2, and 3) that can be ticked or unticked directly from YAML frontmatter or from the Bases table itself. Each of these is just a normal checkbox property — true or false — nothing fancy.
Here’s the simple tick-count formula I’m using:
if(note["1"].isTruthy(), 1, 0) +
if(note["2"].isTruthy(), 1, 0) +
if(note["3"].isTruthy(), 1, 0)
This adds up to a number between 0 and 3, depending on how many checkboxes are ticked.
I’m sorting the table first by Tick Count (ascending) and then by Name (A→Z) — so 0 comes before 1, and everything stays nicely ordered alphabetically within each tick count group.
Previously, this was done using a pretty bloated system that relied on tags to simulate ticks. I had to add #t1, #t2, #t3, or #from3 (The idea behind #from3 was to make it so that when something moved from three ticks back to two, it would go to the bottom of the two-tick section rather than the top.) manually, which worked, but it was clunky and got messy fast once tags started piling up.
Now I just physically tick the boxes. No tags. No extra formulas.
I might eventually refine it further — for example, if I tick checkpoint 2 or 3, it could automatically tick the ones before it — but honestly, it already feels good enough as it is, so I’m leaving that for later.
The other major improvement is in the CSS
The old version was extremely bloated — not the formulas themselves, but the CSS file. It used loads of flex rules and extra layers that were slowing everything down. I stripped almost all of that out and rebuilt it from scratch.
Here’s the previous working version (bloated as heck and made through lots of arguments with GPT who kept repeating the same thing and doing my head in, I had to read through the elements in the console to push GPT in the right direction, the issue i was asking its help for was the width of the columns but it didn’t know how to fix it and only after looking through the console as I tinkered, did I came up with the right idea of changing the global width restrictions. I gave the idea to GPT and it gave me a code that worked but was unnecessarily bloated. Honestly, from here on I’m limiting my use of AI, although it did push me to learn to do it myself out of sheer frustration, which I guess is one way to learn.):
.bases-table-header-icon {
display: none;
}
/* Remove dividers for the actual first three formula columns, regardless of order */
.bases-view [data-property="formula.1"],
.bases-view [data-property="formula.2"],
.bases-view [data-property="formula.3"] {
box-shadow: none !important;
border-inline-end: none !important;
}
.bases-view [data-property="formula.1"]::after,
.bases-view [data-property="formula.2"]::after,
.bases-view [data-property="formula.3"]::after {
content: none !important;
}
.bases-view [data-property="formula.1"] .bases-table-header-resizer,
.bases-view [data-property="formula.2"] .bases-table-header-resizer,
.bases-view [data-property="formula.3"] .bases-table-header-resizer {
opacity: 0 !important;
pointer-events: none;
}
/* centre Booleans or checkboxes */
.bases-view .bases-tbody .bases-tr > *:has(input[type="checkbox"]),
.bases-view .bases-tbody .bases-tr > *:has(.bases-boolean),
.bases-view .bases-tbody .bases-tr > *:has(.mod-boolean) {
text-align: center !important;
}
.bases-view input[type="checkbox"],
.bases-view .bases-boolean,
.bases-view .mod-boolean {
display: inline-block !important;
margin: 0 auto !important;
vertical-align: middle !important;
}
/* Centre headers of the three formula columns */
.bases-view .bases-thead .bases-td.mod-formula .bases-table-header {
justify-content: center !important;
align-items: center !important;
}
.bases-view .bases-thead .bases-td.mod-formula .bases-table-header-name {
text-align: center !important;
}
/* Everything else left-aligned */
.bases-view .bases-thead .bases-td:not(.mod-formula) .bases-table-header {
justify-content: flex-start !important;
align-items: center !important;
}
.bases-view .bases-thead .bases-td:not(.mod-formula) .bases-table-header-name {
text-align: left !important;
}
/* kill min-width CSS variables used by Bases */
.bases-view {
--bases-table-column-min-width: 0px !important;
--table-column-min-width: 0px !important;
}
/* remove hard min-widths on header + resizer layers */
.bases-view .bases-thead .bases-td,
.bases-view .bases-thead .bases-td .bases-table-header,
.bases-view .bases-thead .bases-td .bases-table-header-label,
.bases-view .bases-thead .bases-td .bases-table-header-name,
.bases-view .bases-table-header-resizer {
min-width: 0 !important;
min-inline-size: 0 !important;
}
/* allow columns to shrink below 40 px when dragged */
.bases-view .bases-thead .bases-td {
flex-shrink: 1 !important;
}
/* optional: smaller default width for first three */
.bases-view .bases-td[data-property="formula.1"],
.bases-view .bases-td[data-property="formula.2"],
.bases-view .bases-td[data-property="formula.3"] {
width: 28px !important;
min-width: 28px !important;
max-width: 28px !important;
flex: 0 0 28px !important;
}
/* centre the checkbox inside formula cells by targeting the real node */
.bases-view .bases-tbody .bases-td[data-property^="formula."] > .bases-table-cell.bases-rendered-value:has(> input[type="checkbox"]) {
display: flex !important;
justify-content: center !important;
align-items: center !important;
width: 70% !important;
height: 100% !important;
padding: 0 !important;
margin: 0 !important;
box-sizing: border-box !important;
}
.bases-view .bases-tbody .bases-td[data-property^="formula."] > .bases-table-cell.bases-rendered-value > input[type="checkbox"] {
display: inline-block !important;
margin: 0 !important;
vertical-align: middle !important;
}
The new CSS is lightweight. It hides the header icons, keeps the columns clean, centres everything that needs to be centred, and just generally looks and performs better.
I only kept what was absolutely necessary — nothing redundant. AND I DID IT ALL ON MY OWN:
/* === 1. HIDES HEADER ICONS === */
.bases-table-header-icon {
display: none;
}
/* === 2. REMOVES DIVIDERS FOR FIRST THREE NOTE COLUMNS === */
.bases-view [data-property="note.1"],
.bases-view [data-property="note.2"],
.bases-view [data-property="note.3"] {
box-shadow: none !important;
border-inline-end: none !important;
}
/* === 3. CENTRES CHECKBOXES IN BODY CELLS === */
.bases-view input[type="checkbox"] {
display: inline-block !important;
margin: 0 auto !important;
vertical-align: middle !important;
}
/* === 4. ADDS LEFT MARGIN TO THIRD CHECKBOX COLUMN (Stabalises it a lot better, fits perfectly if the first two column widths are 20px and the last is 23px, ) === */
.bases-view [data-property="note.3"] .bases-table-cell input[type="checkbox"] {
margin-left: 2px !important; /* adjust as needed */
}
/* === 5. CENTREs HEADERS OF THE THREE CHECKBOX COLUMNS === */
.bases-view .bases-thead .bases-td:has(.bases-table-header-icon .lucide-check-square) .bases-table-header {
justify-content: center !important;
}
/* === 6. GLOBAL TABLE CONFIGURATION (COLUMN MIN-WIDTH) === */
.bases-view {
--bases-table-column-min-width: 20px !important;
}
One cool side effect of switching to true/false booleans is that Obsidian visually distinguishes between:
- a box that’s currently ticked (
true), and
- a box that’s been unticked (
false).
- a box that’s never been ticked (empty state),
Respectively:
So even without the old “from3” logic, I can still tell which items I’ve actively unticked — and that’s basically replaced the need for all the extra sorting formulas I used before.
Performance is miles better now, and the setup works exactly how I wanted:
simple, responsive, and progression-based without any tag clutter.
But, um, please do tell me about these progress bars, they sound interesting.