diff --git a/README.md b/README.md
index 11309bc627652388f1630748763847b6edd9a0d8..34e1952a0346f238442b876cc32466de79541b14 100644
--- a/README.md
+++ b/README.md
@@ -31,36 +31,32 @@ go get nhooyr.io/websocket
 - [ ] WebSockets over HTTP/2 [#4](https://github.com/nhooyr/websocket/issues/4)
 - [ ] Deflate extension support [#5](https://github.com/nhooyr/websocket/issues/5)
 
-## Example
+## Examples
 
 For a production quality example that shows off the full API, see the [echo example on the godoc](https://godoc.org/nhooyr.io/websocket#example-package--Echo).
 
 ### Server
 
 ```go
-http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
 	c, err := websocket.Accept(w, r, websocket.AcceptOptions{})
 	if err != nil {
 		// ...
 	}
-	defer c.Close(websocket.StatusInternalError, "")
+	defer c.Close(websocket.StatusInternalError, "the sky is falling")
 
 	ctx, cancel := context.WithTimeout(r.Context(), time.Second*10)
 	defer cancel()
-
-	_, r, err := c.Reader(ctx)
-	if err != nil {
-		// ...
-	}
 	
-	b, err := ioutil.ReadAll(r)
+	var v interface{}
+	err = wsjson.Read(ctx, c, &v)
 	if err != nil {
 		// ...
 	}
 	
-	fmt.Printf("received %q\n", b)
+	log.Printf("received: %v", v)
 	
-	c.Close(websocket.StatusNormalClosure, "")
+	c.Close(websocket.StatusNormalClosure, "success")
 })
 ```
 
@@ -71,27 +67,17 @@ ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
 defer cancel()
 
 c, _, err := websocket.Dial(ctx, "ws://localhost:8080", websocket.DialOptions{})
-if err != nil {
-	return err
-}
-defer c.Close(websocket.StatusInternalError, "")
-
-ww, err := c.Writer(ctx)
-if err != nil {
-	// ...
-}
-
-_, err = w.Write([]byte("hi"))
 if err != nil {
 	// ...
 }
+defer c.Close(websocket.StatusInternalError, "the sky is falling")
 
-err = w.Close()
+err = wsjson.Write(ctx, c, "hi")
 if err != nil {
 	// ...
 }
 
-c.Close(websocket.StatusNormalClosure, "")
+c.Close(websocket.StatusNormalClosure, "done")
 ```
 
 ## Design considerations
diff --git a/ci/bench/Dockerfile b/ci/bench/Dockerfile
index d5ab4a64fa6ac51d559b04be3c8e5af188b3d8d9..a2b6c73d48df7ea5b67c22307ca3b987ce5055b5 100644
--- a/ci/bench/Dockerfile
+++ b/ci/bench/Dockerfile
@@ -1,9 +1,9 @@
 FROM golang:1.12
 
 LABEL "com.github.actions.name"="bench"
-LABEL "com.github.actions.description"="bench"
+LABEL "com.github.actions.description"=""
 LABEL "com.github.actions.icon"="code"
-LABEL "com.github.actions.color"="purple"
+LABEL "com.github.actions.color"="red"
 
 COPY entrypoint.sh /entrypoint.sh
 
diff --git a/ci/fmt/Dockerfile b/ci/fmt/Dockerfile
index 6cd967893310760753a6f9609a243a3edbe8a1b2..f829f51470f1b686891bb9ff8b3e680643c532b2 100644
--- a/ci/fmt/Dockerfile
+++ b/ci/fmt/Dockerfile
@@ -1,9 +1,9 @@
 FROM golang:1.12
 
 LABEL "com.github.actions.name"="fmt"
-LABEL "com.github.actions.description"="fmt"
+LABEL "com.github.actions.description"=""
 LABEL "com.github.actions.icon"="code"
-LABEL "com.github.actions.color"="purple"
+LABEL "com.github.actions.color"="blue"
 
 COPY entrypoint.sh /entrypoint.sh
 
diff --git a/ci/lint/Dockerfile b/ci/lint/Dockerfile
index a1c92a91948d94926bb315fb31b80f8e10103553..9ad3b48254804a0607141c8a3077594568158771 100644
--- a/ci/lint/Dockerfile
+++ b/ci/lint/Dockerfile
@@ -1,9 +1,9 @@
 FROM codercom/playcicache
 
 LABEL "com.github.actions.name"="lint"
-LABEL "com.github.actions.description"="lint"
+LABEL "com.github.actions.description"=""
 LABEL "com.github.actions.icon"="code"
-LABEL "com.github.actions.color"="purple"
+LABEL "com.github.actions.color"="pink"
 
 COPY entrypoint.sh /entrypoint.sh
 
diff --git a/ci/test/Dockerfile b/ci/test/Dockerfile
index ec6c47698f01efd3937695a5f9b41a4adf5c9577..424c56c3721523116edf922181ede2e82e9f058d 100644
--- a/ci/test/Dockerfile
+++ b/ci/test/Dockerfile
@@ -1,9 +1,9 @@
 FROM golang:1.12
 
 LABEL "com.github.actions.name"="test"
-LABEL "com.github.actions.description"="test"
+LABEL "com.github.actions.description"=""
 LABEL "com.github.actions.icon"="code"
-LABEL "com.github.actions.color"="purple"
+LABEL "com.github.actions.color"="green"
 
 RUN apt update && \
 	apt install -y shellcheck python-pip && \
diff --git a/doc.go b/doc.go
index 9c5a077d513531637af252419eb5f3f250146b3a..0b873b48478be8dcbd3d33c406811843428db0ad 100644
--- a/doc.go
+++ b/doc.go
@@ -9,7 +9,7 @@
 // a WebSocket server, Accept to accept a WebSocket client dial and then Conn to interact
 // with the resulting WebSocket connections.
 //
-// The echo example is the best way to understand how to correctly use the library.
+// The examples are the best way to understand how to correctly use the library.
 //
-// The wsjson and wspb packages contain helpers for JSON and ProtoBuf messages.
+// The wsjson and wspb subpackages contain helpers for JSON and ProtoBuf messages.
 package websocket
diff --git a/example_echo_test.go b/example_echo_test.go
index 99b293b22d6c981c06034bcaf3ee2628a73d5346..358f5a202b32f7732fabca39e73d871f17e60bee 100644
--- a/example_echo_test.go
+++ b/example_echo_test.go
@@ -50,18 +50,18 @@ func Example_echo() {
 		}
 	}()
 
-	// Now we dial the server and send the messages.
+	// Now we dial the server, send the messages and echo the responses.
 	err = client("ws://" + l.Addr().String())
 	if err != nil {
 		log.Fatalf("client failed: %v", err)
 	}
 
 	// Output:
-	// 0
-	// 1
-	// 2
-	// 3
-	// 4
+	// received: map[i:0]
+	// received: map[i:1]
+	// received: map[i:2]
+	// received: map[i:3]
+	// received: map[i:4]
 }
 
 // echoServer is the WebSocket echo server implementation.
@@ -74,7 +74,7 @@ func echoServer(w http.ResponseWriter, r *http.Request) error {
 	if err != nil {
 		return err
 	}
-	defer c.Close(websocket.StatusInternalError, "")
+	defer c.Close(websocket.StatusInternalError, "the sky is falling")
 
 	if c.Subprotocol() == "" {
 		c.Close(websocket.StatusPolicyViolation, "cannot communicate with the default protocol")
@@ -138,7 +138,7 @@ func client(url string) error {
 	if err != nil {
 		return err
 	}
-	defer c.Close(websocket.StatusInternalError, "")
+	defer c.Close(websocket.StatusInternalError, "the sky is falling")
 
 	for i := 0; i < 5; i++ {
 		err = wsjson.Write(ctx, c, map[string]int{
@@ -154,9 +154,9 @@ func client(url string) error {
 			return err
 		}
 
-		fmt.Printf("%v\n", v["i"])
+		fmt.Printf("received: %v\n", v)
 	}
 
-	c.Close(websocket.StatusNormalClosure, "")
+	c.Close(websocket.StatusNormalClosure, "done")
 	return nil
 }
diff --git a/example_test.go b/example_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..f5c92bbc4340695a88b1c145562d6bf79ba01fdc
--- /dev/null
+++ b/example_test.go
@@ -0,0 +1,58 @@
+package websocket_test
+
+import (
+	"context"
+	"log"
+	"net/http"
+	"time"
+
+	"nhooyr.io/websocket"
+	"nhooyr.io/websocket/wsjson"
+)
+
+func ExampleAccept() {
+	fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		c, err := websocket.Accept(w, r, websocket.AcceptOptions{})
+		if err != nil {
+			log.Println(err)
+			return
+		}
+		defer c.Close(websocket.StatusInternalError, "the sky is falling")
+
+		ctx, cancel := context.WithTimeout(r.Context(), time.Second*10)
+		defer cancel()
+
+		var v interface{}
+		err = wsjson.Read(ctx, c, &v)
+		if err != nil {
+			log.Println(err)
+			return
+		}
+
+		log.Printf("received: %v", v)
+
+		c.Close(websocket.StatusNormalClosure, "success")
+	})
+
+	http.ListenAndServe("localhost:8080", fn)
+}
+
+func ExampleDial() {
+	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
+	defer cancel()
+
+	c, _, err := websocket.Dial(ctx, "ws://localhost:8080", websocket.DialOptions{})
+	if err != nil {
+		log.Println(err)
+		return
+	}
+	defer c.Close(websocket.StatusInternalError, "the sky is falling")
+
+	err = wsjson.Write(ctx, c, "hi")
+	if err != nil {
+		log.Println(err)
+		return
+	}
+
+	c.Close(websocket.StatusNormalClosure, "done")
+}
diff --git a/statuscode.go b/statuscode.go
index 8c5e5f6ca5a556d0d52cf5a02b7a31657c440024..7ac424ed091a8f4025d6a58ec571606a07775efa 100644
--- a/statuscode.go
+++ b/statuscode.go
@@ -42,7 +42,7 @@ const (
 	statusTLSHandshake
 )
 
-// CloseError represents an error from a WebSocket close frame.
+// CloseError represents a WebSocket close frame.
 // It is returned by Conn's methods when the Connection is closed with a WebSocket close frame.
 type CloseError struct {
 	Code   StatusCode