Making a collaborative Color Grid
Create a color grid and make pixel pictures with friends. As users edit the color grid, changes will be automatically persisted and synced in real-time across clients.

Getting Started
Create a NextJS app and install y-sweet
.
npx create-next-app@latest colorgrid --ts --tailwind
cd colorgrid
npm install @y-sweet/sdk @y-sweet/react
Run your app
npm run dev
Enable state synchronization and persistence
Get a quickstart connection string
If you haven't already, copy your quickstart connection string from the y-sweet dashboard (opens in a new tab). This quickstart connection string is used to connect your app to the y-sweet server that handles state sync and persistence.
getOrCreateDoc
Replace the contents of src/page.tsx
with the following. Pass your quickstart connection string key to getOrCreateDoc
. Change the definition of CONNECTION_STRING
to include your personal “quickstart” connection string.
This connection string is then passed to YDocProvider
, which creates a client-side websocket connection. This is how the client speaks to the y-sweet server.
import { ColorGrid } from './ColorGrid'
import { YDocProvider } from '@y-sweet/react'
import { getOrCreateDoc } from '@y-sweet/sdk'
type HomeProps = {
// searchParams is an object provided by Next.js containing URL parameters.
// See: https://nextjs.org/docs/app/api-reference/file-conventions/page
searchParams: Record<string, string>
}
// ****************************************************************
// ** TO DO: Replace this with your quickstart connection string **
// ****************************************************************
// For simplicity, we are hard-coding the connection string in the
// file. In a real app, you should instead pass this in through a
// secret store or environment variable.
const CONNECTION_STRING = "[paste your connection string]"
export default async function Home({ searchParams }: HomeProps) {
const clientToken = await getOrCreateDoc(searchParams.doc, CONNECTION_STRING)
return (
<YDocProvider clientToken={clientToken} setQueryParam="doc">
<ColorGrid />
</YDocProvider>
)
}
Note that you'll see errors when you run this code, because we haven't defined ColorGrid
. We'll do that in the next section.
The Color Grid
Create a Color Grid
Component
In another file that runs on the client side with use client
, create your color grid component. Using the y-sweet hook useMap
(opens in a new tab) to sync client state and persist it across sessions.
'use client'
import { useMap } from '@y-sweet/react'
const GRID_SIZE = 10
const DEFAULT_COLOR = 'white'
// For simplicty, we'll just add one color option
// We'll add the color picker in the next step
const COLOR_SELECTIONS = ['black']
export function ColorGrid() {
// Initialize our Color Grid as an object of cell positions that map to
// color values. `useMap` returns a Y.Map, and subscribes the component
// to changes in that map.
const items = useMap<string>('colorgrid')
return (
<div className="space-y-3 p-4 lg:p-8">
<div>Color Grid</div>
<table>
<tbody>
{Array.from({ length: GRID_SIZE }, (x, i) => (
<tr key={i}>
{Array.from({ length: GRID_SIZE }, (x, j) => {
// Turn the cell position as a string.
const key = `${i},${j}`
const itemColor = items!.get(key)
return (
<td key={key}>
<div
className="w-8 h-8 cursor-pointer"
style={{ backgroundColor: itemColor || DEFAULT_COLOR }}
onClick={() => items!.set(key, COLOR_SELECTIONS[0])}
/>
</td>
)
})}
</tr>
))}
</tbody>
</table>
</div>
)
}
Add a color picker
- Add a list of hex color codes to
COLOR_SELECTIONS
- Keep selected color in local state, since this information doesn't need to be persisted across clients
- Set the value for a selected cell the active color
onClick
'use client'
import { useMap } from '@y-sweet/react'
import { useState } from 'react'
const GRID_SIZE = 10
const COLOR_SELECTIONS = ['#500724', '#831843', '#9d174d', '#be185d', '#db2777', '#f472b6', '#f9a8d4', null]
const DEFAULT_COLOR = 'white'
export function ColorGrid() {
// Initialize our Color Grid as an object of cell positions that map to
// color values. `useMap` returns a Y.Map, and subscribes the component
// to changes in that map.
const items = useMap<string>('colorgrid')
// Keep the selected color as local state.
const [selectedColor, setSelectedColor] = useState<string | null>(COLOR_SELECTIONS[0])
return (
<div className="space-y-3 p-4 lg:p-8">
<div>Color Grid</div>
<div className="space-x-2 flex flex-row">
{COLOR_SELECTIONS.map((c) => (
<div
key={c}
className={`w-8 h-8 cursor-pointer ring-2 ring-offset-1 ${
c === selectedColor ? 'ring-pink-100' : 'ring-transparent'
}`}
style={{ backgroundColor: c ?? DEFAULT_COLOR }}
onClick={() => setSelectedColor(c)}
/>
))}
</div>
<table>
<tbody>
{Array.from({ length: GRID_SIZE }, (x, i) => (
<tr key={i}>
{Array.from({ length: GRID_SIZE }, (x, j) => {
// Turn the cell position as a string.
const key = `${i},${j}`
const item = items!.get(key)
return (
<td key={key}>
<div
className="w-8 h-8 cursor-pointer"
style={{ backgroundColor: item || DEFAULT_COLOR }}
onClick={() => {
if (selectedColor === null) {
items!.delete(key)
} else {
items!.set(key, selectedColor)
}
}}
/>
</td>
)
})}
</tr>
))}
</tbody>
</table>
</div>
)
}
Run the app
We have enough here to test the to do list.
Run npm run dev
and navigate to localhost:3000
(opens in a new tab) or whichever port you're running on.
When you load the page, you'll notice a ?doc=...
parameter appended to the url. This is a document ID automatically created by getOrCreateDoc
. If this ID is supplied in the url, the same doc will appear. Otherwise, a new doc will be created.
Using the debugger
In the developer tools, you'll find a link to the Y-Sweet Debugger. You can use it to inspect the state of your app.

How to open developer tools:
Next Steps
Refer to our Color Grid demo
You can see all the code in action in our color grid demo (opens in a new tab) and style your Color Grid to match the cover image.