Theme
On this page (7)
Component Skill — components/comment-form/component-skill.md

Native basis

A <form>: the writer's Avatar beside a Textarea that grows with its text (field-sizing: content), optional name / email Inputs for guests, a hint and the submit Button. Native validation - required, minlength, type="email".

Built from

Textarea, Input, Label, Avatar, Button, Tabs, Toggle Group, Separator, Kbd, Alert, Rating.

Web Platform APIs

field-sizing: content:user-invalidrequired / minlength:has()

Classes

.mk-comment-form.mk-comment-form-main.mk-comment-form-fields.mk-comment-form-footer.mk-comment-form-hint.mk-comment-form-toolbar

Data attributes

data-variant="reply"; .mk-comment-form-fields for guest name + email; data-variant="framed"; .mk-comment-form-toolbar.

§Default

The avatar beside a Textarea that grows with its text (field-sizing: content), a hint and the submit Button. Native validation: required + minlength - submit empty and the browser says so.

§Guest

No avatar - the form takes the full width (:has()). Name and email Inputs for guests sit above the textarea; type=email validates natively.

§Reply

data-variant='reply': compact, under a comment - a small avatar and a cancel button.

§Write and preview

data-variant='framed': one box with Tabs (Write / Preview), a formatting Toggle Group, a Separator and a link Button, the growing textarea and a footer with a Kbd shortcut hint.

§Signed out

No form for guests - an Alert with the actions in its place.

§Review with a rating

A review form: the Rating component (native radios) above the growing textarea - required, so posting without stars is blocked by the browser.

§CSS view file

/* -- Comment Form (website) -----------------------------------------
   Collects a new comment or a reply: the writer's avatar beside a Textarea
   that grows with its text (field-sizing), optional name / email fields for
   guests, a hint and the submit Button. Native validation (required,
   minlength) - :user-invalid marks the field only after an attempt. */
@layer components {
  .mk-comment-form {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 0.75rem;
    box-sizing: border-box;
    width: 100%;
    margin: 0;
    & > .avatar { margin-block-start: 0.25rem; }
    /* without an avatar the form takes the full row */
    &:not(:has(> .avatar)) { grid-template-columns: minmax(0, 1fr); }
  }
  .mk-comment-form-main {
    display: flex;
    flex-direction: column;
    gap: 0.625rem;
    min-width: 0;
    & .textarea {
      field-sizing: content;
      min-height: 5.5rem;
      max-height: 20rem;
      resize: none;
    }
  }
  /* guest fields: name + email side by side */
  .mk-comment-form-fields {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
    gap: 0.625rem;
    & > div { display: flex; flex-direction: column; gap: 0.375rem; }
  }
  .mk-comment-form-footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 0.5rem;
  }
  .mk-comment-form-hint {
    margin: 0 auto 0 0;
    color: var(--muted-foreground);
    font-size: 0.75rem;
  }
  /* a formatting row above the text (a Toggle Group, a Separator, Buttons) */
  .mk-comment-form-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.25rem;
  }
  /* framed: toolbar, text and footer in one bordered box - the textarea
     loses its own border, the box takes the focus ring */
  .mk-comment-form[data-variant="framed"] .mk-comment-form-main {
    gap: 0;
    overflow: hidden;
    border: 1px solid var(--input);
    border-radius: var(--radius-lg);
    background: var(--background);
    transition: border-color 120ms, box-shadow 120ms;
    &:focus-within { border-color: var(--ring); box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 25%, transparent); }
    & .mk-comment-form-toolbar { padding: 0.375rem; border-block-end: 1px solid var(--border); background: color-mix(in oklch, var(--muted) 50%, transparent); }
    & .textarea { border: 0; border-radius: 0; box-shadow: none; outline: none; background: transparent; }
    & .mk-comment-form-footer { padding: 0.5rem 0.625rem; border-block-start: 1px solid var(--border); }
    & .tabs { gap: 0; }
    & .tab-content { padding: 0; }
  }
  /* reply: compact, under a comment */
  .mk-comment-form[data-variant="reply"] {
    gap: 0.5rem;
    & .mk-comment-form-main .textarea { min-height: 3.5rem; font-size: 0.875rem; }
  }
  /* -- Accessibility ------------------------------------------- */
  @media (prefers-reduced-motion: reduce) {
    .mk-comment-form[data-variant="framed"] .mk-comment-form-main { transition: none; }
  }
  @media (prefers-contrast: more) {
    .mk-comment-form-hint { color: var(--foreground); }
  }
  @media (forced-colors: active) {
    .mk-comment-form-main .textarea { border: 1px solid CanvasText; }
  }
}

Comments, ideas or improvements? Edit this page's source on GitHub