Fix the broken input focus outline, unify input styling, tokenize scales

The root cause of 'some inputs show a focus outline, most don't': the
global input:focus/textarea:focus/select:focus rule (outline:none,
border-color: accent) has specificity (0,0,1,1). Every per-component
rule shaped '.wrapper input { border: 1px solid var(--border) }' (.form
input, .field-row input, .kanban__new input, .sidebar__select select,
.modal__field select) ties it exactly, and -- being unconditional --
silently won that tie by simply appearing later in the file, so the
input's border stayed var(--border) forever regardless of focus. Only
two controls escaped: .project-card__name-input (a class applied
directly to the element, (0,0,1,0), too low to ever win the tie) and
.card-row__text (has its own .card-row__text:focus, (0,0,2,0),
genuinely higher). Confirmed with a live computed-style test against
the real stylesheet before and after.

Fix: one canonical .field class (plus .field--compact for inline 'add'
rows and the sidebar, .field--autosize for the dashboard's JS-grown
textarea), applied directly to the <input>/<textarea>/<select> itself
in every view -- structurally immune to the same tie, since a bare
class can never out-specificity input:focus. This also collapses five
near-duplicate rules (each with its own padding/radius/font-size) into
one definition, and fixes .kanban__new input's stray
background: var(--surface) (every other field uses var(--bg); this one
nearly matched its own var(--surface) sidebar panel).

Also: .btn-danger hardcoded #b3261e/#fff instead of the already-
existing var(--error)/var(--accent-text) tokens, so danger buttons
didn't adapt in dark mode like every other error-coloured element;
now they do. Added .field:disabled styling (opacity/cursor), matching
buttons -- latent until now since no input bound :disabled yet.

Second pass, promoting repeated-but-consistent raw values to tokens:
- border-radius: 6/7/8/10/12/999px -- the stray 7px (.sidebar__link)
  folded into the 6px tier -- become --radius-sm/md/lg/xl/pill.
- disabled/ghost opacity: 0.6 was used for every disabled button
  except .status-row__delete's 0.4 (now unified) and drag-ghost states'
  0.5 (semantically different, kept separate) -- --opacity-disabled/
  --opacity-ghost.
Font-size and spacing values also repeat but don't reduce to a clean
scale without arbitrary judgement calls either way -- left alone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-04 22:07:35 +01:00
co-authored by Claude Sonnet 5
parent a85bb182c7
commit a3462af005
8 changed files with 145 additions and 113 deletions
+38
View File
@@ -74,6 +74,44 @@ dashboard; the grid and the sidebar dropdown both pick it up via the shared
`projects` store). Signed-out routes `projects` store). Signed-out routes
(`/login`, `/verify-email`) render without the sidebar. (`/login`, `/verify-email`) render without the sidebar.
## Styling
`style.css` is one global stylesheet (no scoped/component styles) with a
handful of conventions worth knowing before adding to it:
- **Tokens** (`:root` custom properties): colours (`--bg`, `--surface`,
`--border`, `--text`, `--muted`, `--accent`(-text), `--error`, `--warn-bg`/
`-border`), a border-radius scale (`--radius-sm` 6px compact controls,
`--radius-md` 8px buttons/inputs, `--radius-lg` 10px tiles, `--radius-xl`
12px panels, `--radius-pill`), and two opacity values (`--opacity-disabled`
0.6, `--opacity-ghost` 0.5 for a dragged item's placeholder). Reach for one
of these before hand-writing a value that's really just "the same grey
border again" or "the same rounding as every other button."
- **`.field`** is the one themed `<input>`/`<textarea>`/`<select>` look, used
everywhere from the login form to the sidebar's project switcher.
`.field--compact` is the same thing smaller, for a control that's a flex
child beside a button (an inline "add" row) or squeezed into the sidebar.
`.field--autosize` adds the `resize:none; overflow:hidden` the dashboard's
JS-driven auto-growing textarea needs. Crucially, **`.field` is applied
directly to the control**, not to a wrapper (`.form input` no longer
exists) -- see the next point for why that's load-bearing, not just style.
- **Put component classes on the control itself, not a wrapping element**,
for anything that needs a `:focus` state. `input:focus`/`textarea:focus`/
`select:focus` (global, removes the default outline and colours the border
with `--accent` instead) has specificity `(0,0,1,1)` -- one pseudo-class,
one element. A rule shaped `.wrapper input { border-color: var(--border) }`
ties it exactly, and being unconditional, silently wins that tie by simply
appearing later in the file -- the input keeps `var(--border)` forever,
focused or not, no matter what `:focus` says. `.field`, applied straight to
the element, is `(0,0,1,0)` -- strictly lower, so it can never win that
tie regardless of source order. (This bit a real shipped version of the
app: half the inputs had a working focus ring and half silently didn't,
purely from which pattern each one happened to use.) A component that
needs its own more elaborate `:focus` state (`.card-row__text`, background
swap included) writes `.card-row__text:focus` explicitly -- specificity
`(0,0,2,0)`, genuinely higher, wins outright rather than by luck of
ordering.
## Inbox ## Inbox
A card with no project lives in the caller's inbox (`useInboxStore`), rendered A card with no project lives in the caller's inbox (`useInboxStore`), rendered
+2 -1
View File
@@ -129,7 +129,7 @@ async function onInboxChange(change: ColumnChange) {
<rect x="6.25" y="2" width="3.5" height="9" rx="1" /> <rect x="6.25" y="2" width="3.5" height="9" rx="1" />
<rect x="11.5" y="2" width="3.5" height="6" rx="1" /> <rect x="11.5" y="2" width="3.5" height="6" rx="1" />
</svg> </svg>
<select v-model="selectedProjectId" aria-label="Go to project"> <select v-model="selectedProjectId" class="field field--compact" aria-label="Go to project">
<option value="" disabled>Choose a project</option> <option value="" disabled>Choose a project</option>
<option v-for="project in projects.projects" :key="project.id" :value="project.id"> <option v-for="project in projects.projects" :key="project.id" :value="project.id">
{{ project.title }} {{ project.title }}
@@ -167,6 +167,7 @@ async function onInboxChange(change: ColumnChange) {
<form class="kanban__new" @submit.prevent="onCreateInboxCard"> <form class="kanban__new" @submit.prevent="onCreateInboxCard">
<input <input
v-model="newInboxText" v-model="newInboxText"
class="field field--compact"
type="text" type="text"
maxlength="1000" maxlength="1000"
required required
+97 -106
View File
@@ -14,6 +14,19 @@
/* Kanban inbox column: a touch lighter than a status column (var(--bg)). */ /* Kanban inbox column: a touch lighter than a status column (var(--bg)). */
--inbox-bg: #fcfcfd; --inbox-bg: #fcfcfd;
color-scheme: light dark; color-scheme: light dark;
/* Same radius used consistently by everything of that kind -- not a full
scale, just names for the handful of values already in use. */
--radius-sm: 6px; /* compact controls: chips, small icon buttons, .field--compact */
--radius-md: 8px; /* buttons, .field, dropdown menus */
--radius-lg: 10px; /* tiles: project cards, kanban columns */
--radius-xl: 12px; /* panels: .card, .sidebar, .modal__dialog */
--radius-pill: 999px; /* status chip */
/* Both used consistently for their one meaning each -- not "disabled at
0.6, ghost at 0.5" by convention, just what those states are. */
--opacity-disabled: 0.6;
--opacity-ghost: 0.5;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
@@ -36,7 +49,14 @@
/* No browser focus ring on form controls -- the border colour change is /* No browser focus ring on form controls -- the border colour change is
highlight enough (components with more elaborate focus styles, e.g. highlight enough (components with more elaborate focus styles, e.g.
.card-row__text, override this with their own higher-specificity rule). */ .card-row__text, override this with their own higher-specificity rule).
Specificity is a single pseudo-class + element, (0,0,1,1) -- .field below
is deliberately a single class with no element in the selector, (0,0,1,0),
so it can never win a specificity tie against this regardless of source
order (a wrapper-class + descendant-element rule, e.g. the old ".form
input", ties with this rule instead and -- being unconditional -- can
silently beat it by simply appearing later in the file, permanently
hiding the focus state; that was a real, previously-shipped bug). */
input:focus, input:focus,
textarea:focus, textarea:focus,
select:focus { select:focus {
@@ -44,6 +64,47 @@ select:focus {
border-color: var(--accent); border-color: var(--accent);
} }
/* The one themed text input/select/textarea, applied directly to the
control itself everywhere -- login, dashboard, project, configuration,
profile, sidebar. See the note above for why "directly to the control"
(a single class, no wrapping selector) matters here, not just for
consistency. */
.field {
display: block;
width: 100%;
box-sizing: border-box;
padding: 0.55rem 0.7rem;
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--bg);
color: var(--text);
font: inherit;
font-size: 1rem;
}
.field:disabled {
opacity: var(--opacity-disabled);
cursor: not-allowed;
}
/* Same control, smaller -- inline "add" rows and the sidebar, where it's a
flex child beside a button or icon rather than filling a form row. */
.field--compact {
flex: 1;
width: auto;
min-width: 0;
padding: 0.4rem 0.5rem;
border-radius: var(--radius-sm);
font-size: 0.9rem;
}
/* The dashboard's project-name field: grown by JS to fit its content, so no
manual resize handle or scrollbar. */
.field--autosize {
resize: none;
overflow: hidden;
}
body { body {
margin: 0; margin: 0;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
@@ -81,7 +142,7 @@ body {
border: 1px solid var(--border); border: 1px solid var(--border);
background: var(--bg); background: var(--bg);
color: var(--text); color: var(--text);
border-radius: 8px; border-radius: var(--radius-md);
padding: 0.35rem 0.55rem; padding: 0.35rem 0.55rem;
font-size: 1rem; font-size: 1rem;
line-height: 1; line-height: 1;
@@ -140,7 +201,7 @@ body {
overflow-y: auto; overflow-y: auto;
padding: 1rem 0.75rem 1.5rem; padding: 1rem 0.75rem 1.5rem;
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 12px; border-radius: var(--radius-xl);
background: var(--surface); background: var(--surface);
} }
@@ -157,7 +218,7 @@ body {
cursor: pointer; cursor: pointer;
font-size: 1rem; font-size: 1rem;
padding: 0.3rem 0.5rem; padding: 0.3rem 0.5rem;
border-radius: 6px; border-radius: var(--radius-sm);
} }
.sidebar__close:hover { .sidebar__close:hover {
@@ -219,7 +280,7 @@ body {
align-items: center; align-items: center;
gap: 0.6rem; gap: 0.6rem;
padding: 0.45rem 0.6rem; padding: 0.45rem 0.6rem;
border-radius: 7px; border-radius: var(--radius-sm);
color: var(--text); color: var(--text);
text-decoration: none; text-decoration: none;
font-size: 0.9rem; font-size: 0.9rem;
@@ -272,18 +333,6 @@ body {
padding: 0.2rem 0.6rem; padding: 0.2rem 0.6rem;
} }
.sidebar__select select {
flex: 1;
min-width: 0;
font: inherit;
font-size: 0.9rem;
padding: 0.35rem 0.4rem;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--bg);
color: var(--text);
}
.sidebar__inbox-cards { .sidebar__inbox-cards {
max-height: 40vh; max-height: 40vh;
overflow-y: auto; overflow-y: auto;
@@ -298,7 +347,7 @@ body {
.card { .card {
background: var(--surface); background: var(--surface);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 12px; border-radius: var(--radius-xl);
padding: 1.75rem; padding: 1.75rem;
} }
@@ -325,7 +374,7 @@ h1 {
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
background: var(--warn-bg); background: var(--warn-bg);
border: 1px solid var(--warn-border); border: 1px solid var(--warn-border);
border-radius: 8px; border-radius: var(--radius-md);
font-size: 0.9rem; font-size: 0.9rem;
} }
@@ -372,7 +421,7 @@ h1 {
cursor: pointer; cursor: pointer;
font-size: 0.9rem; font-size: 0.9rem;
padding: 0.2rem 0.4rem; padding: 0.2rem 0.4rem;
border-radius: 6px; border-radius: var(--radius-sm);
} }
.passkey-notice__dismiss:hover { .passkey-notice__dismiss:hover {
@@ -397,7 +446,7 @@ h1 {
gap: 0.75rem; gap: 0.75rem;
padding: 0.6rem 0.75rem; padding: 0.6rem 0.75rem;
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 8px; border-radius: var(--radius-md);
background: var(--bg); background: var(--bg);
} }
@@ -424,7 +473,7 @@ h1 {
cursor: pointer; cursor: pointer;
font-size: 0.85rem; font-size: 0.85rem;
padding: 0.3rem 0.5rem; padding: 0.3rem 0.5rem;
border-radius: 6px; border-radius: var(--radius-sm);
} }
.passkeys__remove:hover { .passkeys__remove:hover {
@@ -450,7 +499,7 @@ h1 {
padding: 1rem; padding: 1rem;
background: var(--surface); background: var(--surface);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 10px; border-radius: var(--radius-lg);
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
} }
@@ -481,22 +530,6 @@ h1 {
font-size: 0.9rem; font-size: 0.9rem;
} }
.project-card__name-input {
display: block;
width: 100%;
box-sizing: border-box;
padding: 0.55rem 0.7rem;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--bg);
color: var(--text);
font: inherit;
font-size: 1rem;
/* Grown in JS to fit its content -- no manual resize handle, no scrollbar. */
resize: none;
overflow: hidden;
}
/* --- project detail: cards ------------------------------------------ */ /* --- project detail: cards ------------------------------------------ */
.cards { .cards {
@@ -512,7 +545,7 @@ h1 {
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 8px; border-radius: var(--radius-md);
padding: 0.4rem 0.55rem; padding: 0.4rem 0.55rem;
background: var(--surface); background: var(--surface);
} }
@@ -520,7 +553,7 @@ h1 {
.card-row__status { .card-row__status {
flex: none; flex: none;
padding: 0.15rem 0.55rem; padding: 0.15rem 0.55rem;
border-radius: 999px; border-radius: var(--radius-pill);
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 600; font-weight: 600;
white-space: nowrap; white-space: nowrap;
@@ -538,7 +571,7 @@ h1 {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
border: 1px solid transparent; border: 1px solid transparent;
border-radius: 6px; border-radius: var(--radius-sm);
background: transparent; background: transparent;
color: var(--text); color: var(--text);
font-size: 1rem; font-size: 1rem;
@@ -563,7 +596,7 @@ h1 {
cursor: pointer; cursor: pointer;
font-size: 1rem; font-size: 1rem;
padding: 0.2rem 0.4rem; padding: 0.2rem 0.4rem;
border-radius: 6px; border-radius: var(--radius-sm);
} }
.card-row__delete:hover { .card-row__delete:hover {
@@ -625,7 +658,7 @@ h1 {
border: 1px solid var(--border); border: 1px solid var(--border);
background: var(--bg); background: var(--bg);
color: var(--text); color: var(--text);
border-radius: 8px; border-radius: var(--radius-md);
padding: 0.35rem 0.7rem; padding: 0.35rem 0.7rem;
font: inherit; font: inherit;
font-size: 0.9rem; font-size: 0.9rem;
@@ -653,7 +686,7 @@ h1 {
list-style: none; list-style: none;
background: var(--surface); background: var(--surface);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 8px; border-radius: var(--radius-md);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
} }
@@ -667,7 +700,7 @@ h1 {
color: var(--text); color: var(--text);
font: inherit; font: inherit;
padding: 0.45rem 0.6rem; padding: 0.45rem 0.6rem;
border-radius: 6px; border-radius: var(--radius-sm);
cursor: pointer; cursor: pointer;
} }
@@ -733,14 +766,14 @@ h1 {
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 8px; border-radius: var(--radius-md);
padding: 0.5rem 0.6rem; padding: 0.5rem 0.6rem;
background: var(--surface); background: var(--surface);
cursor: grab; cursor: grab;
} }
.status-row--ghost { .status-row--ghost {
opacity: 0.5; opacity: var(--opacity-ghost);
} }
.status-row__name { .status-row__name {
@@ -757,7 +790,7 @@ h1 {
cursor: pointer; cursor: pointer;
font-size: 1rem; font-size: 1rem;
padding: 0.2rem 0.4rem; padding: 0.2rem 0.4rem;
border-radius: 6px; border-radius: var(--radius-sm);
} }
.status-row__delete:hover:not(:disabled) { .status-row__delete:hover:not(:disabled) {
@@ -765,7 +798,7 @@ h1 {
} }
.status-row__delete:disabled { .status-row__delete:disabled {
opacity: 0.4; opacity: var(--opacity-disabled);
cursor: not-allowed; cursor: not-allowed;
} }
@@ -778,23 +811,11 @@ h1 {
margin-top: 0.75rem; margin-top: 0.75rem;
} }
.field-row input {
flex: 1;
min-width: 0;
font: inherit;
font-size: 0.9rem;
padding: 0.4rem 0.5rem;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--bg);
color: var(--text);
}
.field-row button[type='submit'] { .field-row button[type='submit'] {
flex: none; flex: none;
padding: 0.4rem 0.7rem; padding: 0.4rem 0.7rem;
font-size: 0.9rem; font-size: 0.9rem;
border-radius: 6px; border-radius: var(--radius-sm);
} }
.modal__field { .modal__field {
@@ -804,15 +825,6 @@ h1 {
margin-top: 0.75rem; margin-top: 0.75rem;
} }
.modal__field select {
padding: 0.5rem 0.6rem;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--bg);
color: var(--text);
font: inherit;
}
/* --- kanban board ---------------------------------------------------- */ /* --- kanban board ---------------------------------------------------- */
.kanban { .kanban {
@@ -827,7 +839,7 @@ h1 {
flex: 0 0 16rem; flex: 0 0 16rem;
background: var(--bg); background: var(--bg);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 10px; border-radius: var(--radius-lg);
padding: 0.75rem; padding: 0.75rem;
} }
@@ -862,7 +874,7 @@ h1 {
ghost placeholder while something is dragged over it. */ ghost placeholder while something is dragged over it. */
.kanban__cards:empty { .kanban__cards:empty {
border: 1px dashed var(--border); border: 1px dashed var(--border);
border-radius: 8px; border-radius: var(--radius-md);
} }
.kanban__cards:empty::before { .kanban__cards:empty::before {
@@ -879,14 +891,14 @@ h1 {
.kanban-card { .kanban-card {
background: var(--surface); background: var(--surface);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 8px; border-radius: var(--radius-md);
padding: 0.55rem 0.65rem; padding: 0.55rem 0.65rem;
font-size: 0.9rem; font-size: 0.9rem;
cursor: grab; cursor: grab;
} }
.kanban-card--ghost { .kanban-card--ghost {
opacity: 0.5; opacity: var(--opacity-ghost);
} }
.kanban__new { .kanban__new {
@@ -895,23 +907,11 @@ h1 {
margin-top: 0.6rem; margin-top: 0.6rem;
} }
.kanban__new input {
flex: 1;
min-width: 0;
font: inherit;
font-size: 0.9rem;
padding: 0.4rem 0.5rem;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--surface);
color: var(--text);
}
.kanban__new button[type="submit"] { .kanban__new button[type="submit"] {
flex: none; flex: none;
padding: 0.4rem 0.7rem; padding: 0.4rem 0.7rem;
font-size: 0.9rem; font-size: 0.9rem;
border-radius: 6px; border-radius: var(--radius-sm);
} }
/* --- confirmation modal --------------------------------------------------- */ /* --- confirmation modal --------------------------------------------------- */
@@ -939,7 +939,7 @@ h1 {
max-width: 24rem; max-width: 24rem;
background: var(--surface); background: var(--surface);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 12px; border-radius: var(--radius-xl);
padding: 1.5rem; padding: 1.5rem;
} }
@@ -958,7 +958,7 @@ h1 {
.btn-secondary, .btn-secondary,
.btn-danger { .btn-danger {
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 8px; border-radius: var(--radius-md);
padding: 0.5rem 0.9rem; padding: 0.5rem 0.9rem;
font: inherit; font: inherit;
font-size: 0.95rem; font-size: 0.95rem;
@@ -971,14 +971,14 @@ h1 {
} }
.btn-danger { .btn-danger {
background: #b3261e; background: var(--error);
border-color: #b3261e; border-color: var(--error);
color: #fff; color: var(--accent-text);
} }
.btn-secondary:disabled, .btn-secondary:disabled,
.btn-danger:disabled { .btn-danger:disabled {
opacity: 0.6; opacity: var(--opacity-disabled);
cursor: default; cursor: default;
} }
@@ -1000,20 +1000,11 @@ h1 {
font-size: 0.9rem; font-size: 0.9rem;
} }
.form input {
padding: 0.55rem 0.7rem;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--bg);
color: var(--text);
font-size: 1rem;
}
button[type="submit"], button[type="submit"],
.btn-primary { .btn-primary {
padding: 0.6rem 1rem; padding: 0.6rem 1rem;
border: none; border: none;
border-radius: 8px; border-radius: var(--radius-md);
background: var(--accent); background: var(--accent);
color: var(--accent-text); color: var(--accent-text);
font-size: 1rem; font-size: 1rem;
@@ -1028,7 +1019,7 @@ button[type="submit"],
button[type="submit"]:disabled, button[type="submit"]:disabled,
.btn-primary:disabled { .btn-primary:disabled {
opacity: 0.6; opacity: var(--opacity-disabled);
cursor: progress; cursor: progress;
} }
+1 -1
View File
@@ -85,7 +85,7 @@ async function onCreate() {
<textarea <textarea
ref="titleInput" ref="titleInput"
v-model="title" v-model="title"
class="project-card__name-input" class="field field--autosize"
rows="1" rows="1"
maxlength="255" maxlength="255"
placeholder="Name" placeholder="Name"
+1 -1
View File
@@ -70,7 +70,7 @@ async function onPasskeyLogin() {
<form v-else class="form" @submit.prevent="onSubmit"> <form v-else class="form" @submit.prevent="onSubmit">
<label> <label>
<span>Email</span> <span>Email</span>
<input v-model="email" type="email" autocomplete="email" required /> <input v-model="email" class="field" type="email" autocomplete="email" required />
<small v-if="error?.fieldError('email')" class="field-error"> <small v-if="error?.fieldError('email')" class="field-error">
{{ error.fieldError('email') }} {{ error.fieldError('email') }}
</small> </small>
+2 -2
View File
@@ -141,7 +141,7 @@ async function onRemovePasskey(passkey: Passkey) {
<form class="form" @submit.prevent="onChangeEmail"> <form class="form" @submit.prevent="onChangeEmail">
<label> <label>
<span>New email</span> <span>New email</span>
<input v-model="newEmail" type="email" maxlength="255" required /> <input v-model="newEmail" class="field" type="email" maxlength="255" required />
<small v-if="changeError?.fieldError('email')" class="field-error"> <small v-if="changeError?.fieldError('email')" class="field-error">
{{ changeError.fieldError('email') }} {{ changeError.fieldError('email') }}
</small> </small>
@@ -197,7 +197,7 @@ async function onRemovePasskey(passkey: Passkey) {
<form class="form form--new-card" @submit.prevent="onAddPasskey"> <form class="form form--new-card" @submit.prevent="onAddPasskey">
<label> <label>
<span>Label</span> <span>Label</span>
<input v-model="newLabel" type="text" maxlength="100" /> <input v-model="newLabel" class="field" type="text" maxlength="100" />
</label> </label>
<p v-if="addError" class="form-error">{{ addError }}</p> <p v-if="addError" class="form-error">{{ addError }}</p>
<button type="submit" :disabled="adding"> <button type="submit" :disabled="adding">
+3 -1
View File
@@ -201,6 +201,7 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKeydown))
<form class="field-row" @submit.prevent="onRenameProject"> <form class="field-row" @submit.prevent="onRenameProject">
<input <input
v-model="nameDraft" v-model="nameDraft"
class="field field--compact"
type="text" type="text"
maxlength="255" maxlength="255"
required required
@@ -251,6 +252,7 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKeydown))
<form class="field-row" @submit.prevent="onAddStatus"> <form class="field-row" @submit.prevent="onAddStatus">
<input <input
v-model="newStatusName" v-model="newStatusName"
class="field field--compact"
type="text" type="text"
maxlength="100" maxlength="100"
required required
@@ -282,7 +284,7 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKeydown))
<label class="modal__field"> <label class="modal__field">
<span>Move cards to</span> <span>Move cards to</span>
<select v-model="reassignTo"> <select v-model="reassignTo" class="field">
<option value="" disabled>Choose a status</option> <option value="" disabled>Choose a status</option>
<option v-for="s in reassignOptions" :key="s.id" :value="s.id">{{ s.name }}</option> <option v-for="s in reassignOptions" :key="s.id" :value="s.id">{{ s.name }}</option>
</select> </select>
+1 -1
View File
@@ -194,7 +194,7 @@ async function onCreate() {
<form class="form form--new-card" @submit.prevent="onCreate"> <form class="form form--new-card" @submit.prevent="onCreate">
<label> <label>
<span>New card</span> <span>New card</span>
<input v-model="newText" type="text" maxlength="1000" required /> <input v-model="newText" class="field" type="text" maxlength="1000" required />
</label> </label>
<button type="submit" :disabled="submitting"> <button type="submit" :disabled="submitting">
{{ submitting ? 'Adding…' : 'Add card' }} {{ submitting ? 'Adding…' : 'Add card' }}