tutorial // Jun 24, 2022

How to Automatically Resize a Textarea to Fit Its Content

How to write a function for automatically resizing a textarea when its content expands past its default height.

How to Automatically Resize a Textarea to Fit Its Content

For this tutorial, we're going to use CheatCode's full-stack JavaScript framework, Joystick. Joystick brings together a front-end UI framework with a Node.js back-end for building apps.

To begin, we'll want to install Joystick via NPM. Make sure you're using Node.js 16+ before installing to ensure compatibility (give this tutorial a read first if you need to learn how to install Node.js or run multiple versions on your computer):

Terminal

npm i -g @joystick.js/cli

This will install Joystick globally on your computer. Once installed, next, let's create a fresh project:

Terminal

joystick create app

After a few seconds, you will see a message logged out to cd into your new project and run joystick start:

Terminal

cd app && joystick start

After this, your app should be running and we're ready to get started.

Writing an autoresize function

In order to make our code as flexible as possible, to start, we're going to write a reusable module for resizing textarea elements. Inside of the /lib folder created for you when you ran joystick create app, add a new file autoResize.js:

/lib/autoResize.js

export default (DOMNode = {}, defaultHeight = 100) => {
  // We'll handle the resize here...
};

To begin, from this file we want to export a function that takes two arguments:

  1. DOMNode which is the JavaScript DOM node representing the <textarea><textarea> element we want to control the height of.
  2. defaultHeight which is the minimum height to set for the textarea if the height of its content does not force an increase in height.

/lib/autoResize.js

export default (DOMNode = {}, defaultHeight = 100) => {
  if (DOMNode) {
    // We'll handle the resize logic here...
  }
};

Next, we want to limit our logic to only run if a DOMNode was passed to the function. This is important as our function will be called whenever an input's content changes, meaning, if for some reason we made a mistake and didn't pass a DOMNode (or we passed the wrong value), we'd be triggering runtime errors on every keypress—no bueno.

/lib/autoResize.js

export default (DOMNode = {}, defaultHeight = 100) => {
  if (DOMNode) {
    const DOMNodeStyles = window.getComputedStyle(DOMNode);
    const paddingTop = parseInt(DOMNodeStyles?.getPropertyValue('padding-top') || 0, 10);
    const paddingBottom = parseInt(DOMNodeStyles?.getPropertyValue('padding-bottom') || 0, 10);

    DOMNode.style.height = `${defaultHeight}px`;
    DOMNode.style.height = `${DOMNode.scrollHeight - paddingTop - paddingBottom}px`;
  }
};

Above, we've added the entirety of our autoresize logic (this will make it easier to step through).

First, because we're dealing with height, we need to anticipate any padding our input might have set in its CSS. To get it, we call to the window.getComputedStyle() function, passing in our DOMNode to get back an object containing all of the styles applied to our <textarea></textarea>.

With those DOMNodeStyles, next, we want to get the top and bottom padding value for the textarea as these will affect the height of the input visually. To get them, we call to the .getPropertyValue() method on the DOMNodeStyles object we just created, passing either padding-top or padding-bottom.

Notice that we pass the call to that method directly into a call to parseInt(). This is because the value we get back from this method is a string containing px (i.e., if our padding-top was 10px we'd get back "10px") and we want it as a plain integer like 10.

Once we have these values stored in paddingTop and paddingBottom, we can move on to adjusting the height of our textarea.

To do it, we need to directly modify the style object of the textarea, setting its height property. We want to do this in two steps: first, setting the default height and then setting the height relative to the current content of the textarea.

We want to do this because we need to account for the input being cleared by the user. If this happens after the input has had enough content to expand the height, upon clearing, the input will retain the set height (as the scroll height hasn't changed).

To set the height, we directly set the height property on the input with DOMNode.style.height, setting it first equal to the concatenation of the defaultHeight variable and px like ${defaultHeight}px. Next, we repeat the same pattern, this time setting the input to its current scrollHeight, subtracting the paddingTop and paddingBottom values we obtained a few lines above and then again, concatenating the resulting integer with px.

That's it! Now, let's put this to use in our UI and see how it all wires up.

Using the autoresize function

This is the easy part. To keep things simple, we're going to modify an existing UI component created for us when we ran joystick create app earlier. Let's open up the component at /ui/pages/index/index.js and make a few changes:

/ui/pages/index/index.js

import ui from '@joystick.js/ui';

const Index = ui.component({
  css: `
    label {
      display: block;
      font-size: 16px;
      margin-bottom: 10px;
    }

    textarea {
      display: block;
      resize: none;
      width: 300px;
      height: 100px;
      border: 1px solid #ddd;
      padding: 10px;
    }
  `,
  render: () => {
    return `
      <form>
        <label>What's your life story?</label>
        <textarea name="lifeStory"></textarea>
      </form>
    `;
  },
});

export default Index;

Replacing the existing contents of the file with the above, to start we want to get the <textarea></textarea> we want to auto-adjust the height of rendered on screen. Down in the render() function of our component, we return a string of HTML containing a simple <form></form> element with a <label></label> and a <textearea></textarea> in it.

Just above this, to make our UI look a bit nicer, we've added some CSS for our <label></label> and <textarea></textarea> elements. Of note: pay attention to the height property for the textarea being set to 100px. This is important .This ensures that when our input first renders, its visual height matches the defaultHeight we're passing to autoResize().

/ui/pages/index/index.js

import ui from '@joystick.js/ui';
import autoResize from '../../../lib/autoResize';

const Index = ui.component({
  events: {
    'input textarea': (event) => {
      autoResize(event.target, 100);
    },
  },
  css: `...`,
  render: () => {
    return `
      <form>
        <label>What's your life story?</label>
        <textarea name="lifeStory"></textarea>
      </form>
    `;
  },
});

export default Index;

Now for the important part. Above, we've added an events property to our component. On the object passed here, we've added an event listener for the input event on our textarea element. Inside of the callback function fired when the event is detected, using the autoResize() function we imported above, we pass in the event.target—or, our <textarea></textarea> element—followed by the default height we want to set in case the textarea is cleared, 100.

That'll do it. Now if we open up our browser to http://localhost:2600 (make sure your app is running), we should see our <textarea></textarea> expand if the content gets past the default height.

Wrapping up

In this tutorial, we learned how to wire up an autoresize function that dynamically adjusts the height of a textarea based on its content. We learned how to manipulate the height of an input on the fly, using its style attribute, making sure to account for changes in padding. Finally, we learned how to put our function to use in response to an input event on our <textarea></textarea>.

Written By
Ryan Glover

Ryan Glover

CEO/CTO @ CheatCode