Clean Markup

Auto-Expanding Form Fields without Scripts using CSS field-sizing

Auto-Expanding Form Fields without Scripts using CSS field-sizing

Creating form controls that organically adapt their height and width to user content has been a persistent struggle in frontend interface design. Native HTML <textarea> elements inherit fixed dimensions dictated by their default rows and cols attributes, enclosing multiline inputs inside cramped boxes with unnecessary vertical scrollbars. Single-line <input> fields suffer from similar restrictions, masking lengthy emails or search queries once typed characters exceed fixed widths. For decades, web developers resorted to complex JavaScript routines to mimic auto-resizing textareas: polling element.scrollHeight on every keystroke, constantly recalculating inline styles, or synchronizing input text into hidden, offscreen <span> clones styled with matching typography.

The CSS Basic User Interface Module Level 4 standardizes this functionality through the field-sizing property. By setting field-sizing: content, browsers compute the dimensions of form controls intrinsically based on their current text content rather than legacy fixed geometry defaults. As users type or delete paragraphs, the form field naturally expands and shrinks without requiring DOM event listeners or triggering layout thrashing. Furthermore, field-sizing: content cooperates cleanly with standard CSS bounding properties such as min-height, max-height, and line-height units (lh), ensuring smooth scrolling boundaries once fields reach maximum design limits.

Example

/* Enable automatic vertical growth for multiline textareas */
textarea.auto-expanding {
  field-sizing: content;
  width: 100%;
  min-height: 2lh;
  max-height: 12lh;
  resize: none;
}

/* Enable automatic horizontal growth for compact search inputs */
input[type="text"].auto-width {
  field-sizing: content;
  min-width: 12ch;
  max-width: 40ch;
}

Implementation

<!-- Interactive Chat & Feedback Composer -->
<div class="composer-container">
  <form class="composer-form">
    <div class="composer-field-group">
      <label for="topic-input" class="composer-label">Topic</label>
      <input
        type="text"
        id="topic-input"
        class="input-inline-grow"
        placeholder="Type a subject..."
        maxlength="60"
      />
    </div>

    <div class="composer-field-group">
      <label for="message-input" class="composer-label">Message</label>
      <textarea
        id="message-input"
        class="textarea-content-grow"
        placeholder="Write your note or question here..."
        rows="1"
      ></textarea>
    </div>

    <footer class="composer-actions">
      <button type="submit" class="composer-submit">Send Message</button>
    </footer>
  </form>
</div>

<style>
/* Base Composer Layout */
.composer-container {
  max-width: 680px;
  margin: 2.5rem auto;
  padding: 1.5rem;
  background-color: #18181b;
  border: 1px solid #27272a;
  border-radius: 0.75rem;
  color: #fafafa;
  box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3);
}

.composer-field-group {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-bottom: 1.25rem;
}

.composer-label {
  font-size: 0.875rem;
  font-weight: 500;
  color: #a1a1aa;
}

/* Auto-Expanding Single-Line Input */
.input-inline-grow {
  font-family: inherit;
  font-size: 1rem;
  padding: 0.5rem 0.75rem;
  background-color: #27272a;
  color: #f4f4f5;
  border: 1px solid #3f3f46;
  border-radius: 0.375rem;
  outline: none;

  /* Native intrinsic sizing */
  field-sizing: content;
  min-width: 15ch;
  max-width: 100%;
  align-self: flex-start;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.input-inline-grow:focus-visible {
  border-color: #6366f1;
  box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.25);
}

/* Auto-Expanding Multiline Textarea */
.textarea-content-grow {
  font-family: inherit;
  font-size: 1rem;
  line-height: 1.5;
  padding: 0.75rem 1rem;
  background-color: #27272a;
  color: #f4f4f5;
  border: 1px solid #3f3f46;
  border-radius: 0.5rem;
  outline: none;
  width: 100%;
  box-sizing: border-box;

  /* Pure CSS auto-expansion with content-based sizing */
  field-sizing: content;
  min-height: 3lh;  /* Starts comfortably at 3 lines */
  max-height: 15lh; /* Caps growth at 15 lines before scrolling */
  resize: none;     /* Manual resize handles are unnecessary */
  overflow-y: auto; /* Activates internal scroll only when max-height is reached */
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.textarea-content-grow:focus-visible {
  border-color: #6366f1;
  box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.25);
}

/* Progressive Enhancement Fallback for older browsers */
@supports not (field-sizing: content) {
  .textarea-content-grow {
    min-height: 6rem;
    resize: vertical; /* Allow manual drag resize when field-sizing is unsupported */
  }

  .input-inline-grow {
    width: 100%;
  }
}

.composer-actions {
  display: flex;
  justify-content: flex-end;
}

.composer-submit {
  padding: 0.625rem 1.25rem;
  font-size: 0.9375rem;
  font-weight: 600;
  color: #ffffff;
  background-color: #4f46e5;
  border: none;
  border-radius: 0.375rem;
  cursor: pointer;
  transition: background-color 0.2s ease;
}

.composer-submit:hover {
  background-color: #4338ca;
}
</style>

Main Point: The CSS field-sizing content declaration enables inputs and textareas to expand dynamically with user input purely through native browser layout calculation without JavaScript height polling.

Credits

Photo by Joel Filipe on Unsplash

← Home