How to create a working download button ?

In my previous projects codewrite and codewrite-2. There was a need for a download button that downloaded some text. The projects are built using react and I will show you how you can do the same.


HTML, CSS and JS only

Here is a sample download button using just plain html,css and js:

Sample


Code

For the code the html, css is very simple. I am also using bootstrap but you can style them however you want:

<textarea id="downloadableText" style="width:100%;padding-left:0.5rem">Your text here</textarea>
<div style="width:100%; display:flex; flex-direction:row; gap:0.25rem;">
<input type="text" id="fileName" value="fileName" required style="width:100%; padding-left:0.5rem;"/>
<button class="btn btn-dark" onclick="downloadText()" style="gap:0.5rem;display:flex;flex-direction:row; align-items:center;">

<!-- Download Icon -->
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512">
<!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path style="fill:rgb(220,220,220);"  d="M288 32c0-17.7-14.3-32-32-32s-32 14.3-32 32V274.7l-73.4-73.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l128 128c12.5 12.5 32.8 12.5 45.3 0l128-128c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L288 274.7V32zM64 352c-35.3 0-64 28.7-64 64v32c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V416c0-35.3-28.7-64-64-64H346.5l-45.3 45.3c-25 25-65.5 25-90.5 0L165.5 352H64zm368 56a24 24 0 1 1 0 48 24 24 0 1 1 0-48z"/>
</svg>

Download
</button>
</div>

The actual code is the javascript:

function downloadText()
{
    let downloadableValue=document.getElementById("downloadableText").innerText;
    let fileName=document.getElementById("fileName").value;
    const link = document.createElement('a');
    const content = new Blob([downloadableValue], { type: `text;charset=utf-8` });
    link.href = URL.createObjectURL(content);
    link.download = fileName;
    link.click();
    URL.revokeObjectURL(link.href);
    link.remove();
}

Explanation

let downloadableValue=document.getElementById("downloadableText").innerText;

Reading the text to download.

let fileName=document.getElementById("fileName").value

Reading the value of the input to use it as filename when downloading.

const link = document.createElement('a');

We create a temporary link element that will be used to download the resource.

const content = new Blob([downloadableValue], { type: text;charset=utf-8 });

We then create a use Blob class used to create immutable, raw data (binary large object) file.The array contains the data to be included in the Blob. downloadableValue could be a string, typed array, or other binary data. The { type: 'text;charset=utf-8' } specifies the MIME type of the Blob's content. You can find about common MIME Types here.

link.href = URL.createObjectURL(content);

When we create a Blob (e.g., a file, image, or other binary data) and use URL.createObjectURL(blob) to generate a temporary URL that points to the Blob's data, it allocates resources in the browser to keep that Blob accessible. The url of the resource is then used as the href of the link element.

URL.revokeObjectURL(link.href);

The generated object URL remains valid until you explicitly revoke it with URL.revokeObjectURL(). If we don’t revoke it, those resources stay allocated, leading to memory leaks over time, especially if we create many Blob URLs.


React

Creating this in react is also the same, we can use the same function and attach it to the onClick handler. We can create controlled input or simply use an uncontrolled input for the text inputs.

The function will remain the same.

  const downloadText = () => {
    const downloadableValue = fileContent;
    const link = document.createElement('a');
    const content = new Blob([downloadableValue], {
      type: 'text/plain;charset=utf-8',
    });
    link.href = URL.createObjectURL(content);
    link.download = fileName;
    link.click();
    URL.revokeObjectURL(link.href);
    link.remove();
  };

The component code will be:

const [fileContent, setFileContent] = useState('Your text here');
const [fileName, setFileName] = useState('fileName');

return (
    <>
      <textarea
        id='downloadableText'
        style={{ width: '100%', paddingLeft: '0.5rem' }}
        value={fileContent}
        onChange={e => setFileContent(e.target.value)}
      />
      <div
        style={{
          width: '100%',
          display: 'flex',
          flexDirection: 'row',
          gap: '0.25rem',
        }}
      >
        <input
          type='text'
          id='fileName'
          value={fileName}
          onChange={e => setFileName(e.target.value)}
          required
          style={{ width: '100%', paddingLeft: '0.5rem' }}
        />
        <button
          className='btn btn-dark'
          onClick={downloadText}
          style={{
            gap: '0.5rem',
            display: 'flex',
            flexDirection: 'row',
            alignItems: 'center',
          }}
        >
          <svg
            xmlns='http://www.w3.org/2000/svg'
            height='16'
            width='16'
            viewBox='0 0 512 512'
          >
            <path
              style={{ fill: 'rgb(220,220,220)' }}
              d='M288 32c0-17.7-14.3-32-32-32s-32 14.3-32 32V274.7l-73.4-73.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l128 128c12.5 12.5 32.8 12.5 45.3 0l128-128c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L288 274.7V32zM64 352c-35.3 0-64 28.7-64 64v32c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V416c0-35.3-28.7-64-64-64H346.5l-45.3 45.3c-25 25-65.5 25-90.5 0L165.5 352H64zm368 56a24 24 0 1 1 0 48 24 24 0 1 1 0-48z'
            />
          </svg>
          Download
        </button>
      </div>
    </>
  );

The final code will look like:

import { useState } from 'react';

export function App() {
  return <DownloadComponent />;
}



const DownloadComponent = () => {
  const [fileContent, setFileContent] = useState('Your text here');
  const [fileName, setFileName] = useState('fileName');


  const downloadText = () => {
    const downloadableValue = fileContent;
    const link = document.createElement('a');
    const content = new Blob([downloadableValue], {
      type: 'text/plain;charset=utf-8',
    });
    link.href = URL.createObjectURL(content);
    link.download = fileName;
    link.click();
    URL.revokeObjectURL(link.href);
    link.remove();
  };

  return (
    <div>
      <textarea
        id='downloadableText'
        style={{ width: '100%', paddingLeft: '0.5rem' }}
        value={fileContent}
        onChange={e => setFileContent(e.target.value)}
      />
      <div
        style={{
          width: '100%',
          display: 'flex',
          flexDirection: 'row',
          gap: '0.25rem',
        }}
      >
        <input
          type='text'
          id='fileName'
          value={fileName}
          onChange={e => setFileName(e.target.value)}
          required
          style={{ width: '100%', paddingLeft: '0.5rem' }}
        />
        <button
          className='btn btn-dark'
          onClick={downloadText}
          style={{
            gap: '0.5rem',
            display: 'flex',
            flexDirection: 'row',
            alignItems: 'center',
          }}
        >
          <svg
            xmlns='http://www.w3.org/2000/svg'
            height='16'
            width='16'
            viewBox='0 0 512 512'
          >
            <path
              style={{ fill: 'rgb(220,220,220)' }}
              d='M288 32c0-17.7-14.3-32-32-32s-32 14.3-32 32V274.7l-73.4-73.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l128 128c12.5 12.5 32.8 12.5 45.3 0l128-128c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L288 274.7V32zM64 352c-35.3 0-64 28.7-64 64v32c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V416c0-35.3-28.7-64-64-64H346.5l-45.3 45.3c-25 25-65.5 25-90.5 0L165.5 352H64zm368 56a24 24 0 1 1 0 48 24 24 0 1 1 0-48z'
            />
          </svg>
          Download
        </button>
      </div>
    </div>
  );
};

export default DownloadComponent;

Check out my other blogs if you found this useful.

Happy coding 🫡