diff --git a/autobahn_test.go b/autobahn_test.go
index e56a4912db8fa0d4a77930bf371b24640720d727..1bfb141978d02ef04dfd060f954b4efdeb38da54 100644
--- a/autobahn_test.go
+++ b/autobahn_test.go
@@ -6,7 +6,7 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net"
 	"os"
 	"os/exec"
@@ -146,7 +146,7 @@ func wstestCaseCount(ctx context.Context, url string) (cases int, err error) {
 	if err != nil {
 		return 0, err
 	}
-	b, err := ioutil.ReadAll(r)
+	b, err := io.ReadAll(r)
 	if err != nil {
 		return 0, err
 	}
@@ -161,7 +161,7 @@ func wstestCaseCount(ctx context.Context, url string) (cases int, err error) {
 }
 
 func checkWSTestIndex(t *testing.T, path string) {
-	wstestOut, err := ioutil.ReadFile(path)
+	wstestOut, err := os.ReadFile(path)
 	assert.Success(t, err)
 
 	var indexJSON map[string]map[string]struct {
@@ -206,7 +206,7 @@ func unusedListenAddr() (_ string, err error) {
 }
 
 func tempJSONFile(v interface{}) (string, error) {
-	f, err := ioutil.TempFile("", "temp.json")
+	f, err := os.CreateTemp("", "temp.json")
 	if err != nil {
 		return "", fmt.Errorf("temp file: %w", err)
 	}
diff --git a/conn_test.go b/conn_test.go
index c2c4129236d362bbc0bdda55091508ccaf781b11..d97236863421fdb8997dd6589476b07e0fffd31a 100644
--- a/conn_test.go
+++ b/conn_test.go
@@ -7,7 +7,6 @@ import (
 	"context"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"os"
@@ -174,7 +173,7 @@ func TestConn(t *testing.T) {
 			return n2.Close()
 		})
 
-		b, err := ioutil.ReadAll(n1)
+		b, err := io.ReadAll(n1)
 		assert.Success(t, err)
 
 		_, err = n1.Read(nil)
@@ -205,7 +204,7 @@ func TestConn(t *testing.T) {
 			return nil
 		})
 
-		_, err := ioutil.ReadAll(n1)
+		_, err := io.ReadAll(n1)
 		assert.Contains(t, err, `unexpected frame type read (expected MessageBinary): MessageText`)
 
 		select {
diff --git a/dial.go b/dial.go
index 7a7787ff71fafb89321fadbb4f4b33ccc1fa93c6..2889a37bfb3820ba3230bb2068d2db892abcc1e3 100644
--- a/dial.go
+++ b/dial.go
@@ -10,7 +10,6 @@ import (
 	"encoding/base64"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"net/url"
 	"strings"
@@ -114,9 +113,9 @@ func dial(ctx context.Context, urls string, opts *DialOptions, rand io.Reader) (
 			})
 			defer timer.Stop()
 
-			b, _ := ioutil.ReadAll(r)
+			b, _ := io.ReadAll(r)
 			respBody.Close()
-			resp.Body = ioutil.NopCloser(bytes.NewReader(b))
+			resp.Body = io.NopCloser(bytes.NewReader(b))
 		}
 	}()
 
diff --git a/dial_test.go b/dial_test.go
index 28c255c652d71eb9002243f2aa9617357d1c69e8..89ca3075fda50e938ca137c655d0f5d35a878934 100644
--- a/dial_test.go
+++ b/dial_test.go
@@ -6,7 +6,6 @@ import (
 	"context"
 	"crypto/rand"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"strings"
@@ -75,7 +74,7 @@ func TestBadDials(t *testing.T) {
 		_, _, err := Dial(ctx, "ws://example.com", &DialOptions{
 			HTTPClient: mockHTTPClient(func(*http.Request) (*http.Response, error) {
 				return &http.Response{
-					Body: ioutil.NopCloser(strings.NewReader("hi")),
+					Body: io.NopCloser(strings.NewReader("hi")),
 				}, nil
 			}),
 		})
@@ -97,7 +96,7 @@ func TestBadDials(t *testing.T) {
 			return &http.Response{
 				StatusCode: http.StatusSwitchingProtocols,
 				Header:     h,
-				Body:       ioutil.NopCloser(strings.NewReader("hi")),
+				Body:       io.NopCloser(strings.NewReader("hi")),
 			}, nil
 		}
 
diff --git a/doc.go b/doc.go
index efa920e3b61e9c8b61dc6e8548935b3bac26c7f1..43c7d92dbb61268f36daccedce39f1271265dc42 100644
--- a/doc.go
+++ b/doc.go
@@ -16,7 +16,7 @@
 //
 // More documentation at https://nhooyr.io/websocket.
 //
-// Wasm
+// # Wasm
 //
 // The client side supports compiling to Wasm.
 // It wraps the WebSocket browser API.
@@ -25,8 +25,8 @@
 //
 // Some important caveats to be aware of:
 //
-//  - Accept always errors out
-//  - Conn.Ping is no-op
-//  - HTTPClient, HTTPHeader and CompressionMode in DialOptions are no-op
-//  - *http.Response from Dial is &http.Response{} with a 101 status code on success
+//   - Accept always errors out
+//   - Conn.Ping is no-op
+//   - HTTPClient, HTTPHeader and CompressionMode in DialOptions are no-op
+//   - *http.Response from Dial is &http.Response{} with a 101 status code on success
 package websocket // import "nhooyr.io/websocket"
diff --git a/examples/chat/chat.go b/examples/chat/chat.go
index 532e50f544fbc39f2ca0a870b0c9cc7ef8ae8e44..9d393d873c3a343557ead1e62872c6f3e0e8098b 100644
--- a/examples/chat/chat.go
+++ b/examples/chat/chat.go
@@ -3,7 +3,7 @@ package main
 import (
 	"context"
 	"errors"
-	"io/ioutil"
+	"io"
 	"log"
 	"net/http"
 	"sync"
@@ -98,7 +98,7 @@ func (cs *chatServer) publishHandler(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 	body := http.MaxBytesReader(w, r.Body, 8192)
-	msg, err := ioutil.ReadAll(body)
+	msg, err := io.ReadAll(body)
 	if err != nil {
 		http.Error(w, http.StatusText(http.StatusRequestEntityTooLarge), http.StatusRequestEntityTooLarge)
 		return
diff --git a/read.go b/read.go
index 89a009887192247658b2cdb330f99355eb45b1f6..97a4f987dd10c2425a8a7a6fa42eea3b4df98f5c 100644
--- a/read.go
+++ b/read.go
@@ -8,7 +8,6 @@ import (
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"strings"
 	"time"
 
@@ -38,7 +37,7 @@ func (c *Conn) Read(ctx context.Context) (MessageType, []byte, error) {
 		return 0, nil, err
 	}
 
-	b, err := ioutil.ReadAll(r)
+	b, err := io.ReadAll(r)
 	return typ, b, err
 }