nocodb/nocodb
Reviewed against Rams quality heuristics: accessibility, color, typography, spacing, components, motion, UX, and craft.
30 files reviewed·August 1, 2026
Elevated
Design risk in this codebase.
More findings
Verdict
A copy-pasted debug template spread its flaws across nearly every file: unlabeled inputs, unguarded fade-ins, repeated ad-hoc sizing. The real fire is a runtime-built Tailwind class that silently kills the color swatch, the harness's only visible output.
Files Rams reviewed
packages/nc-gui/pages/playground/components/MockInjection.vue
packages/nc-gui/pages/playground/components/filter/filter-group/index.vue
packages/nc-gui/pages/playground/components/filter/filter-row/index.vue
packages/nc-gui/pages/playground/components/row-color-picker-toolbar/filter-type-option/index.vue
packages/nc-gui/pages/playground/components/row-color-picker-toolbar/using-filter-panel/index.vue
packages/nc-gui/components/dashboard/TreeView/Views/List.vue
packages/nc-gui/pages/playground/components/advance-color-picker/index.vue
packages/nc-gui/pages/playground/components/base-icon-color-picker/index.vue
packages/nc-gui/pages/playground/components/filter/index.vue
packages/nc-gui/pages/playground/components/index.vue
packages/nc-gui/pages/playground/components/row-color-picker-toolbar/index.vue
packages/nc-gui/pages/playground/components/row-color-picker-toolbar/using-single-select-panel/index.vue
packages/nc-gui/components/dashboard/TreeView/Views/index.vue
packages/nocodb/src/services/mail/templates/components/ContentWrapper.tsx
packages/nocodb/src/services/mail/templates/components/Footer.tsx
packages/nocodb/src/services/mail/templates/components/RootWrapper.tsx
packages/nc-gui/components/dashboard/TreeView/Views/Node.vue
packages/nc-gui/pages/account/index.vue
packages/nc-gui/pages/admin/index.vue
packages/nc-gui/pages/forgot-password.vue
packages/nc-gui/pages/index.vue
packages/nc-gui/pages/index/[typeOrId]/[baseId]/index.vue
packages/nc-gui/pages/index/[typeOrId]/[baseId]/index/index.vue
packages/nc-gui/pages/index/[typeOrId]/[baseId]/index/index/index.vue
packages/nc-gui/pages/index/[typeOrId]/form/[viewId].vue
packages/nc-gui/pages/index/[typeOrId]/form/[viewId]/index.vue
packages/nc-gui/pages/index/[typeOrId]/form/[viewId]/index/index.vue
packages/nc-gui/pages/index/[typeOrId]/shared/[erdUuid]/index.vue
packages/nc-gui/pages/index/[typeOrId]/view/[viewId].vue
packages/nc-gui/pages/playground/icons/index.vue
Components
packages/nc-gui/pages/playground/components/advance-color-picker/index.vue:11
Color swatch never renders because the Tailwind class is built at runtime
The swatch div at line 11 in advance-color-picker/index.vue binds `:class="[`bg-${color1}`]"` to construct a background class from the `color1` ref. Tailwind's JIT scanner only generates classes it can find as complete strings in source, so `bg-${color1}` is invisible to the build and no CSS rule is ever emitted for it, no matter what color1 resolves to.
Why it matters
The swatch stays blank regardless of what color is picked, so this demo component fails at its one job: showing the selected color.
Fix
Bind the resolved color as an inline style instead of interpolating it into a Tailwind class name.
<div class="inline-block min-h-[24px] min-w-[24px] h-[24px] w-[24px] rounded-md" :class="[`bg-${color1}`]"></div><div class="inline-block min-h-[24px] min-w-[24px] h-[24px] w-[24px] rounded-md" :style="{ backgroundColor: color1 }"></div>Accessibility
packages/nc-gui/pages/playground/components/filter/filter-group/index.vue:137
Debug number inputs for Index, NestedLevel, and filter limits have no accessible label
The inputs bound to `options1.index`, `options1.nestedLevel`, `options1.filterPerViewLimit`, and `options1.filtersCount` (lines 137-153) are each preceded only by plain text like "Index:" with no `<label for>` or `aria-label` tying it to the control.
Why it matters
A screen reader announces these as unlabeled number fields, so a user relying on assistive tech can't tell what each input controls without visually scanning the surrounding text.
Fix
Associate each input with its text using aria-label or a proper label element.
Index: <input v-model="options1.index" type="number" class="text-xs p-1 border-nc-border-gray-medium" /><br /><label for="filter-group-index">Index:</label>
<input id="filter-group-index" v-model="options1.index" type="number" class="text-xs p-1 border-nc-border-gray-medium" aria-label="Index" /><br />Get this score on every PR.
Rams reviews each pull request on your repo and posts inline one-click fixes — about a minute per review.
Install Rams freeSpacing
packages/nc-gui/pages/playground/components/filter/filter-group/index.vue:159
Debug JSON panels repeat the same arbitrary sizing values instead of a shared class
Both event-log panels at lines 159 and 162 use the identical `min-w-[300px] max-h-[200px]` combination with `bg-nc-bg-gray-dark overflow-y-scroll`.
Why it matters
Repeating the same arbitrary pixel values in two places instead of naming them once means any future adjustment to this panel size has to be made twice, and the pattern will keep spreading as more debug panels get copy-pasted.
Fix
Extract the repeated arbitrary size and treatment into one shared utility class or component.
<div class="min-w-[300px] max-h-[200px] overflow-wrap bg-nc-bg-gray-dark overflow-y-scroll">
<pre>{{ JSON.stringify(lastRowChangeEvent1, null, 2) }}</pre>
</div><div class="debug-panel">
<pre>{{ JSON.stringify(lastRowChangeEvent1, null, 2) }}</pre>
</div>
<!-- .debug-panel { @apply min-w-[300px] max-h-[200px] overflow-wrap bg-nc-bg-gray-dark overflow-y-scroll; } -->Motion
packages/nc-gui/pages/playground/components/row-color-picker-toolbar/filter-type-option/index.vue:18
Fade-in animation on both filter and select panels ignores reduced-motion preference
Both the filter panel (line 18) and the select panel (line 24) apply `animate-animated animate-fadeIn` with `animation-duration: 0.3s` and neither is wrapped in a `prefers-reduced-motion` check.
Why it matters
Users who set the OS-level reduced-motion preference to avoid vestibular symptoms still get the fade playing on both panels every time they open this control, since the animation is unconditional.
Fix
Gate decorative animation classes behind a prefers-reduced-motion media query so motion-sensitive users get an instant transition instead.
<div class="bg-nc-bg-default animate-animated animate-fadeIn" style="animation-duration: 0.3s"><div class="bg-nc-bg-default motion-safe:animate-animated motion-safe:animate-fadeIn" style="animation-duration: 0.3s">Typography
Color
UX
Craft
Working well
- Every debug output panel (filter1, lastChangeEvent1, deleted1LastEvent) shares the same bg-nc-bg-gray-dark and overflow treatment, so the state panels read as one consistent system rather than one-off styling per panel.
- Grouping the toggle switches ("disabled", "isLockedView", "disableAddNewFilter") and selects into a compact two-column layout keeps this debug harness scannable even with a large number of options exposed at once.
- Using v-model:row-coloring-mode on SmartsheetToolbarRowColorFilterTypeOption keeps the toolbar's state binding idiomatic Vue rather than manually wiring props and emits.
Scored August 1, 2026 with Rams Engine v0.0.3 · Engine changelog
First scored July 9, 2026: 98/100. This rescore on v0.0.3: 59/100.
This page is an automated design review of nocodb/nocodb’s UI code: 30 files read against 291 versioned rules covering accessibility, color, typography, spacing, components, UX, motion, and craft. The score is out of 100; confirmed criticals cap it — one at 59, two at 49, three or more at 39.
More design scores
Score your own repo.
Free on public repos, no account. The same engine that scored this page reads your UI code and mints a score page like this one.
Public repos only. The full engine reviews the UI code and mints a public score page — we email you the link too. Already-scored repos open instantly.