Skip to content

โ† All Skills Knowledge History

Notebook lined-paper UI for the Memo create/edit modal

completed๐Ÿ“ฆ my-memo๐Ÿ‘ค YCEJ9999๐Ÿ—“ 2026-06-20 22:30:00

memoui-stylinglined-paper-textareaunocssnative-css-src-assetsrule-13-ui-priorityshadow-dom-limitationcss-variable-theming

Request

  • The basic add/edit memo form is too plain โ€” make the modal look like a real memo: a borderless 'blank' title and a lined notebook-paper writing area, with a pushpin and category tint.
  • Write the finding to Mono Skills.

Summary

Notebook lined-paper UI for the Memo modal (mono-vue remote)

How to give a form a realistic "notebook paper" look inside a mono-modal, the right way under Template Rule 13, and the one trick that makes text sit on the lines.

Goal

The plain stacked Add/Edit memo form (mono-input + mono-textarea + select + mono-switch) looked too basic. Turn the modal body into a sheet of ruled notebook paper: a borderless "blank" title line, blue horizontal rules with the content resting on them, a red left margin, a pushpin to pin, and the category tinting the page.

Why native <input>/<textarea> here (Rule 13)

mono-textarea / mono-input are shadow-DOM Lit web components โ€” you cannot paint a ruled-line background onto the field inside their shadow root from outside. Rule 13's UI priority is: mono component โ†’ its .mono-* CSS โ†’ UnoCSS โ†’ native CSS in src/assets/, imported only into the page that needs it. A lined-paper field is exactly that last-resort case, so the writing surface uses native <input> + <textarea> styled from a scoped stylesheet. We still keep mono-modal (the popup shell) and mono-button (footer).

Native elements also simplify binding: plain v-model="store.input.title" on the reactive store object โ€” no need for the mono-* CustomEvent reader ($event.detail.modelValue). The search box stays a mono-input and still uses that reader.

The key trick โ€” text that rests on the lines

A lined textarea = line-height matched to a repeating-linear-gradient, with background-attachment: local so the rules scroll with the text:

.memo-paper__content {
  --memo-line: 30px;                 /* MUST equal line-height */
  line-height: var(--memo-line);
  background-image: repeating-linear-gradient(
    to bottom,
    transparent 0,
    transparent calc(var(--memo-line) - 1px),
    #c9d6e8 calc(var(--memo-line) - 1px),
    #c9d6e8 var(--memo-line)
  );
  background-attachment: local;      /* rules scroll WITH the text, not fixed */
  border: none; outline: none; resize: vertical;
  background-color: transparent;
}

If the rules and text drift, adjust --memo-line and the field's top padding together โ€” the text baseline must land just above each gradient rule. background-attachment: local is what keeps the rules aligned while the textarea scrolls.

The rest of the sheet

  • Scope everything under a .memo-paper root class. The stylesheet is a global import (import '@my-memo/assets/memo-paper.css'), so namespacing under one root prevents leakage into other pages / the federated example routes. Verified the list + other pages are unchanged.
  • Red margin = .memo-paper::before absolute left rule at --memo-margin (44px); fields get padding-left: calc(var(--memo-margin) + 12px) to clear it.
  • Borderless title = native <input> with border: none; background: transparent, a heavier border-bottom, bold + a handwriting font stack (Segoe Print/Bradley Hand/Comic Sans MS).
  • Pushpin = an absolute top-right <button> toggling store.input.pinned; icon swaps i-mdi-pin โ‡„ i-mdi-pin-outline (UnoCSS presetIcons / @iconify/json mdi), tilts + tints when pinned.
  • Category tint = a single CSS variable --memo-accent, set inline from a memoCategoryAccent: Record<string,string> map kept in src/datas/tables/memo.ts (Rule 9). The paper root uses the selected category's accent (margin + pin); each category pill sets its own accent so the pill shows its color; the active pill fills with it.

Files

  • src/assets/memo-paper.css (new) โ€” all paper styling, namespaced under .memo-paper.
  • src/datas/tables/memo.ts โ€” added memoCategoryAccent (static data in datas, Rule 9).
  • src/pages/memo/index.vue โ€” modal body swapped to the .memo-paper structure; dropped the now-unused mono-helper/ui/textarea + mono-helper/ui/switch imports; added the CSS import.

Gotchas worth keeping

  • color-mix(in srgb, var(--memo-accent) 60%, #cbd2dd) gives per-category pill/border tints from one accent var (modern browsers / Vite dev โ€” fine here).
  • Importing a plain .css file in an SFC injects it globally (not <style scoped>), so the root-class namespacing is what keeps it contained โ€” that's the Rule 13-compliant pattern (file in src/assets, imported only by the one page).

Outcome

successvite dev server + module transform + UnoCSS icon check GET /memo, the page module, and /src/assets/memo-paper.css all return 200 (no compile error) Compiled page references .memo-paper, .memo-paper__title/__content, memoCategoryAccent UnoCSS generates i-mdi-pin and i-mdi-pin-outline for the pushpin toggle No leftover mono-textarea / mono-switch references after the swap

Decisions

Native input/textarea for the lined paper, not mono-textarea

Built the writing surface from native <input>/<textarea> styled by a scoped src/assets stylesheet; kept mono-modal + mono-button.

Why: mono-textarea is a shadow-DOM web component โ€” its internal field can't receive a ruled-line background from outside. Rule 13's last tier (native CSS in src/assets, imported only into the page) is the sanctioned path for this. Native elements also bind with plain v-model on the reactive store.

Lined effect via line-height-matched repeating-linear-gradient + background-attachment: local

One CSS var --memo-line drives both line-height and the gradient period; background-attachment: local keeps rules aligned while the textarea scrolls.

Why: This is the reliable way to make typed text rest on ruled lines that move with the content; mismatched line-height/gradient is what makes text drift off the lines.

Scope all paper CSS under a .memo-paper root class

Namespaced every selector under .memo-paper instead of using <style scoped> or global rules.

Why: A plain CSS import in an SFC is injected globally; with federated example routes also present, root-class namespacing prevents style leakage. Verified other pages and the memo list are unchanged.

Category accent as a single CSS variable from a datas map

memoCategoryAccent in src/datas/tables/memo.ts; set --memo-accent inline; margin, pin, and selected pill all read it.

Why: Keeps static data in datas (Rule 9) and themes the whole sheet from one variable, so adding a category is a one-line change.

Files changed

FileOperationNote
src/assets/memo-paper.cssaddedNotebook lined-paper styling scoped under .memo-paper: ruled-line gradient textarea, red margin, borderless title, pushpin, category pills; tinted via --memo-accent
src/datas/tables/memo.tsmodifiedAdded memoCategoryAccent: Record<string,string> (per-category hex accents) โ€” static data in datas (Rule 9)
src/pages/memo/index.vuemodifiedSwapped modal body to native .memo-paper structure (v-model title/textarea, pushpin toggle, category pills); imported memo-paper.css + memoCategoryAccent; removed now-unused mono-helper/ui/textarea + /switch imports

Commands

pnpm dev
curl /memo, /src/pages/memo/index.vue, /src/assets/memo-paper.css (all 200)
MONO_SKILLS=true MONO_SKILLS_GITHUB_TOKEN=[REDACTED] pnpm exec mono skills save --app my-memo --dir .mono/skills/pending/<id>

Validation

Tests: not run Build: passed


โ† All Skills Knowledge History