typescript 45 lines · 7 steps

Clipboard copy with a legacy fallback

Prefer the modern async Clipboard API, but fall back to a hidden textarea when it isn't available.

Explained by highlit
1export async function copyToClipboard(text: string): Promise<boolean> {
2 if (navigator.clipboard && window.isSecureContext) {
3 try {
4 await navigator.clipboard.writeText(text);
5 return true;
6 } catch {
7 return legacyCopy(text);
8 }
9 }
10 
11 return legacyCopy(text);
12}
13 
14function legacyCopy(text: string): boolean {
15 const textarea = document.createElement("textarea");
16 textarea.value = text;
17 textarea.setAttribute("readonly", "");
18 textarea.style.position = "fixed";
19 textarea.style.top = "-9999px";
20 textarea.style.opacity = "0";
21 
22 document.body.appendChild(textarea);
23 
24 const selection = document.getSelection();
25 const previousRange = selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : null;
26 
27 textarea.select();
28 textarea.setSelectionRange(0, text.length);
29 
30 let succeeded = false;
31 try {
32 succeeded = document.execCommand("copy");
33 } catch {
34 succeeded = false;
35 }
36 
37 document.body.removeChild(textarea);
38 
39 if (previousRange && selection) {
40 selection.removeAllRanges();
41 selection.addRange(previousRange);
42 }
43 
44 return succeeded;
45}
01 / 01
STEP 01

Walkthrough

Space play ←→ step click any line
Three takeaways
  1. 1Feature-detect capable APIs and degrade gracefully instead of assuming support.
  2. 2Offscreen DOM elements let you drive legacy browser commands without visible flicker.
  3. 3Restoring prior state after a side effect keeps the operation invisible to the user.

Related explainers

typescript
import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';
import localeFrExtra from '@angular/common/locales/extra/fr';
import localeDe from '@angular/common/locales/de';

Locale-aware bootstrapping in Angular

i18n localization dependency-injection
Intermediate 8 steps
python
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
 
app = FastAPI()
 

Building a WebSocket chat with FastAPI

websockets broadcast connection-management
Intermediate 9 steps
typescript
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import * as Joi from 'joi';
 

Validating env config at boot in NestJS

configuration schema-validation environment-variables
Intermediate 8 steps
typescript
import { Inject, Injectable, Logger } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { InjectRepository } from '@nestjs/typeorm';

A cache-aside country lookup in NestJS

cache-aside dependency-injection batch-lookup
Intermediate 8 steps
rust
use axum::{
    extract::{Path, State},
    response::sse::{Event, KeepAlive, Sse},
};

Streaming import progress with SSE in Axum

server-sent-events streams watch-channel
Advanced 7 steps
typescript
import { Injectable, effect, signal, computed } from '@angular/core';
 
interface Preferences {
  theme: 'light' | 'dark';

A signal-based preferences store in Angular

signals state-management persistence
Intermediate 7 steps

Share this explainer

Here's the card — post it anywhere.

Clipboard copy with a legacy fallback — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code