<form id="idea" class="idea-form" action="">
  <svg class="idea-form__icon" viewBox="0 0 32 32" width="32px" height="32px" aria-hidden="true">
    <g fill="currentcolor">
      <path d="M16,2A10,10,0,0,0,6,12a9.19,9.19,0,0,0,3.46,7.62c1,.93,1.54,1.46,1.54,2.38h2c0-1.84-1.11-2.87-2.19-3.86A7.2,7.2,0,0,1,8,12a8,8,0,0,1,16,0,7.2,7.2,0,0,1-2.82,6.14c-1.07,1-2.18,2-2.18,3.86h2c0-.92.53-1.45,1.54-2.39A9.18,9.18,0,0,0,26,12,10,10,0,0,0,16,2Z" />
      <rect x="11" y="24" width="10" height="2"/>
      <rect x="13" y="28" width="6" height="2"/>
    </g>
  </svg>
  <button class="idea-form__btn idea-form__btn--start" type="button" data-toggle>I have an idea</button>
  <div class="idea-form__fill"></div>
  <div class="idea-form__content">
    <label for="my-idea" class="idea-form__label">Idea</label>
    <textarea id="my-idea" class="idea-form__textarea" placeholder="My idea is…"></textarea>
    <button class="idea-form__btn" type="submit" disabled>Submit</button>
  </div>
</form>
* {
  border: 0;
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}
:root {
  --hue: 53;
  --bg: hsl(var(--hue),10%,90%);
  --fg: hsl(var(--hue),10%,10%);
  --primary: hsl(var(--hue),90%,50%);
  --trans-dur: 0.3s;
  --trans-timing1: cubic-bezier(0.65,0,0.35,1);
  --trans-timing2: cubic-bezier(0.65,0,0.35,1.35);
  font-size: calc(14px + (30 - 14) * (100vw - 280px) / (3840 - 280));
}
body,
button,
textarea {
  font: 1em/1.5 Montserrat, sans-serif;
}
body {
  background-color: var(--bg);
  color: var(--fg);
  display: flex;
  height: 100vh;
  transition:
    background-color var(--trans-dur),
    color var(--trans-dur);
}
.idea-form {
  background-color: hsl(0,0%,0%);
  color: hsl(0,0%,100%);
  margin: auto;
  overflow: hidden;
  position: relative;
  width: 16.5em;
  height: 4.5em;
  transition:
    background-color var(--trans-dur),
    color var(--trans-dur),
    height var(--trans-dur) var(--trans-timing2);

  &,
  &__btn,
  &__content {
    border-radius: 1.5em;
  }
  &,
  &__btn {
    position: relative;
  }
  &__btn,
  &__textarea {
    outline: transparent;
    transition:
      background-color var(--trans-dur),
      box-shadow var(--trans-dur),
      opacity var(--trans-dur) var(--trans-timing1);
    -webkit-appearance: none;
    appearance: none;
    -webkit-tap-highlight-color: transparent;

    &:disabled {
      cursor: not-allowed;
      opacity: 0.3;
    }
  }
  &__btn {
    background-color: hsl(0,0%,0%);
    box-shadow: 0 0 0 0.25em hsla(var(--hue),90%,30%,0);
    color: hsl(0,0%,100%);
    cursor: pointer;
    display: flex;
    margin-inline-start: auto;
    padding: 0.5em 1em;
    z-index: 2;

    &--start {
      background-color: transparent;
      box-shadow: none;
      color: currentColor;
      display: flex;
      align-items: center;
      letter-spacing: 0.0625em;
      padding-inline-start: 5.25em;
      width: 100%;
      height: 4.5em;
      text-transform: uppercase;
      transition:
        opacity var(--trans-dur) var(--trans-timing1),
        visibility var(--trans-dur) steps(1,start);
    }
    &:focus-visible {
      box-shadow: 0 0 0 0.25em hsla(var(--hue),90%,30%,1);
    }
  }
  &__content {
    opacity: 0;
    padding: 1.5em 1em 0.75em;
    padding-inline-start: 5.25em;
    position: absolute;
    top: 0;
    left: 0;
    visibility: hidden;
    transition:
      opacity var(--trans-dur) var(--trans-timing1),
      visibility var(--trans-dur) steps(1,end);
  }
  &__fill,
  &__icon,
  &__label {
    position: absolute;
  }
  &__fill {
    background-color: hsl(var(--hue),90%,50%);
    border-radius: 50%;
    top: 1.75em;
    left: 2em;
    width: 1em;
    height: 1em;
    transform: translateY(50%) scale(0);
    transition: transform var(--trans-dur) var(--trans-timing1);

    [dir="rtl"] & {
      right: 2em;
      left: auto;
    }
  }
  &__btn--start:focus-visible {
    box-shadow: none;
  }
  &__btn--start:focus-visible + &__fill,
  &__btn--start:hover + &__fill {
    transform: translateY(0) scale(1);
  }
  &__icon {
    color: currentColor;
    display: block;
    top: 0.75em;
    left: 1em;
    width: 3em;
    height: 3em;
    z-index: 1;

    [dir="rtl"] & {
      right: 1em;
      left: auto;
    }
  }
  &__label {
    overflow: hidden;
    width: 1px;
    height: 1px;
  }
  &__textarea {
    background-color: transparent;
    color: hsl(0,0%,0%);
    display: block;
    margin-bottom: 0.75em;
    resize: none;
    width: 100%;
    height: 3em;

    &::placeholder {
      color: hsl(var(--hue),10%,50%);
    }
  }
  // expanded
  &[data-expanded="true"] {
    background-color: transparent;
    height: 8.5em;
    transition-timing-function: steps(1,end), ease, var(--trans-timing2);
  }
  &[data-expanded="true"] &__btn--start {
    opacity: 0;
    pointer-events: none;
    transition-timing-function: var(--trans-timing1), steps(1,end);
    visibility: hidden;
  }
  &[data-expanded="true"] &__content {
    opacity: 1;
    transition-timing-function: var(--trans-timing1), steps(1,start);
    visibility: visible;
  }
  &[data-expanded="true"] &__fill {
    transform: translateY(0) scale(32);
  }
}

/* Dark theme */
@media (prefers-color-scheme: dark) {
  :root {
    --bg: hsl(var(--hue),10%,10%);
    --fg: hsl(var(--hue),10%,90%);
  }
  .idea-form {
    background-color: hsl(0,0%,100%);
    color: hsl(0,0%,0%);
  }
}
View Compiled
window.addEventListener("DOMContentLoaded",() => {
  const ideaForm = new IdeaForm("#idea");
});

class IdeaForm {
  /** Form used for this component */
  form: HTMLFormElement | null;
  /** Timeout function for submission */
  timeout = 0;

  private _idea = "";
  get idea(): string {
    return this._idea;
  }
  set idea(value: string) {
    this._idea = value;

    const submitBtn = this.form?.querySelector("[type=submit]") as HTMLButtonElement;

    if (submitBtn) {
      submitBtn.disabled = value.length === 0;
    }
  }

  private _expanded = false;
  get expanded(): boolean {
    return this._expanded;
  }
  set expanded(value: boolean) {
    this._expanded = value;
    this.form?.setAttribute("data-expanded",`${value}`);
  }

  private _state = SubmitState.Default;
  get state() {
    return this._state;
  }
  set state(value: SubmitState) {
    this._state = value;

    const textarea = this.form?.querySelector("#my-idea") as HTMLTextAreaElement;
    const submitBtn = this.form?.querySelector("[type=submit]") as HTMLButtonElement;

    if (textarea) {
      textarea.disabled = value !== SubmitState.Default;
    }
    if (submitBtn) {
      if (value === SubmitState.Sending) {
        submitBtn.textContent = Label.Sending;
        submitBtn.disabled = true;
      } else if (value === SubmitState.Done) {
        submitBtn.textContent = Label.Sent;
      } else {
        submitBtn.textContent = Label.Submit;
      }
    }
  }
  /**
   * @param el CSS selector of the form
   */
  constructor(el: string) {
    this.form = document.querySelector(el);
    this.form?.addEventListener("click",this.toggle.bind(this));
    this.form?.addEventListener("input",this.ideaUpdate.bind(this));
    this.form?.addEventListener("submit",this.ideaSubmit.bind(this));
    document.addEventListener("click",this.outsideToCollapse.bind(this));
    document.addEventListener("keydown",this.escToCollapse.bind(this));
  }
  /**
   * Click outside the form to collapse.
   * @param e Click event
   * */
  outsideToCollapse(e: Event): void {
    if (this.state !== SubmitState.Default) return;

    let parent: HTMLElement | null = e.target as HTMLElement;

    while (parent !== null) {
      if (parent === this.form) {
        return;
      }
      parent = parent.parentElement;
    }
    this.expanded = false;
  }
  /**
   * Hide the form by pressing Esc.
   * @param e Keyboard event
   * */
  escToCollapse(e: KeyboardEvent): void {
    if (e.code === "Escape" && this.state === SubmitState.Default) {
      this.expanded = false;
    }
  }
  /**
   * Show or hide the form.
   * @param e Click event
   * */
  toggle(e: Event): void {
    const button = e.target as HTMLButtonElement;

    if (button.hasAttribute("data-toggle")) {
      this.expanded = !this.expanded;

      if (this.expanded) {
        const textarea = this.form?.querySelector("#my-idea") as HTMLTextAreaElement;
        textarea?.focus();
      }
    }
  }
  /**
   * Submit the idea content.
   * @param e Submit event
   * */
  async ideaSubmit(e: SubmitEvent): Promise<void> {
    e.preventDefault();

    if (this.state !== SubmitState.Default) return;

    const delaySending = 1000;
    const delayDone = 600;
    const delayReset = 300;

    this.state = SubmitState.Sending;

    return await new Promise<void>(resolve => {
      // send
      clearTimeout(this.timeout);
      this.timeout = setTimeout(() => {
        resolve();
      },delaySending);
    }).then(async () => {
      // submitted
      this.state = SubmitState.Done;

      return await new Promise<void>(resolve => {
        clearTimeout(this.timeout);
        this.timeout = setTimeout(() => {
          resolve();
        },delayDone);
      });
    }).then(() => {
      // collapse and reset
      this.expanded = false;

      clearTimeout(this.timeout);
      this.timeout = setTimeout(() => {
        this.form?.reset();
        this.idea = "";
        this.state = SubmitState.Default;
      },delayReset);
    });
  }
  /**
   * Update the idea content internally.
   * @param e Input event
   * */
  ideaUpdate(e: Event): void {
    const textarea = e.target as HTMLTextAreaElement;
    this.idea = textarea.value;
  }
}
const enum Label {
  Sending = "Sending…",
  Sent = "Sent",
  Submit = "Submit"
}
const enum SubmitState {
  Default = 0,
  Sending,
  Done
}
View Compiled

External CSS

  1. https://fonts.googleapis.com/css2?family=Montserrat&amp;display=swap

External JavaScript

This Pen doesn't use any external JavaScript resources.