Idea for docs: Add support for playground persistence #832
Description
Describe the feature request
Lately, I've been using Claude to prototype a UI, then convert it to StyleX afterward. This works surprisingly well, but I'd love to not rely exclusively on Claude. ChatGPT doesn't support something like artifacts for rendering UI yet.
That said, the new playground opens up the possibility to quickly generate code using any LLM based on the source code provided in the playground and quickly paste the results. This turns out to work!
What I'm hoping for now is simply the possibility for persistence in the playground tool.
Some ideas:
- SolidJS's playground used to use a string algorithm (I think it was called Lezer string compression?) to inline the code in the URL (ugly but works and no server required)
- Tailwind Play does the traditional thing which is persist to a server and gives you a simple URL to easily copy, bookmark, etc.
This was basically one shot by Claude once it understood it should use StyleX as opposed to Tailwind. I'd really like to be able to persist playgrounds such as this so I can scaffold dozens of ideas and bookmark them or share them for example on social media.
View Code Example
import { useState } from 'react';
import * as stylex from '@stylexjs/stylex';
export default function TodoApp() {
const [todos, setTodos] = useState([]);
const [newTodo, setNewTodo] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (!newTodo.trim()) return;
setTodos([...todos, {
id: Date.now(),
text: newTodo.trim(),
completed: false
}]);
setNewTodo('');
};
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<div {...stylex.props(layout.container)}>
<div {...stylex.props(card.container)}>
<form {...stylex.props(form.container)} onSubmit={handleSubmit}>
<div {...stylex.props(form.inputGroup)}>
<input
type="text"
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
placeholder="Add a new todo..."
{...stylex.props(form.input)}
/>
<button type="submit" {...stylex.props(button.primary)}>
Add Todo
</button>
</div>
</form>
<div {...stylex.props(todos.container)}>
{todos.map(todo => (
<div key={todo.id} {...stylex.props(todos.item)}>
<label {...stylex.props(todos.label)}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
{...stylex.props(todos.checkbox)}
/>
<span {...stylex.props(todos.text, todo.completed && todos.completed)}>
{todo.text}
</span>
</label>
<button
onClick={() => deleteTodo(todo.id)}
{...stylex.props(button.delete)}
>
Delete
</button>
</div>
))}
</div>
{todos.length === 0 && (
<p {...stylex.props(text.empty)}>
No todos yet. Add some tasks above!
</p>
)}
</div>
</div>
);
}
/* General color scheme */
const LIGHT_MODE = '@media (prefers-color-scheme: light)';
/* Typography Styles */
const text = stylex.create({
title: {
fontSize: '1.8rem',
marginBottom: '0.5em',
textAlign: 'center',
},
description: {
fontSize: '0.9rem',
marginBottom: '1.5em',
color: '#666',
textAlign: 'center',
},
empty: {
textAlign: 'center',
color: '#666',
marginTop: '2em',
fontSize: '0.9rem',
},
});
/* Layout Styles */
const layout = stylex.create({
container: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: '2em',
minHeight: '100vh',
},
});
/* Card Styles */
const card = stylex.create({
container: {
backgroundColor: {
default: '#1e1e1e',
[LIGHT_MODE]: '#fff',
},
borderRadius: 8,
padding: '2em',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
maxWidth: '800px',
width: '100%',
},
});
/* Form Styles */
const form = stylex.create({
container: {
marginBottom: '2em',
},
inputGroup: {
display: 'flex',
gap: '1em',
},
input: {
flex: 1,
padding: '0.5em',
borderRadius: 4,
border: '1px solid #ccc',
fontSize: '1em',
':focus': {
outline: 'none',
borderColor: '#646cff',
},
},
});
/* Button Styles */
const button = stylex.create({
primary: {
padding: '0.6em 1.2em',
fontSize: '1em',
fontWeight: 500,
borderRadius: 4,
border: 'none',
backgroundColor: {
default: '#646cff',
[LIGHT_MODE]: '#747bff',
},
color: '#fff',
cursor: 'pointer',
transition: 'background-color 0.2s',
':hover': {
backgroundColor: '#535bf2',
},
},
delete: {
padding: '0.4em 0.8em',
fontSize: '0.9em',
borderRadius: 4,
border: 'none',
backgroundColor: '#ff4444',
color: '#fff',
cursor: 'pointer',
transition: 'background-color 0.2s',
':hover': {
backgroundColor: '#cc0000',
},
},
});
/* Todos Styles */
const todos = stylex.create({
container: {
display: 'flex',
flexDirection: 'column',
gap: '0.8em',
},
item: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '0.8em',
backgroundColor: {
default: '#2a2a2a',
[LIGHT_MODE]: '#f5f5f5',
},
borderRadius: 4,
},
label: {
display: 'flex',
alignItems: 'center',
gap: '0.8em',
flex: 1,
},
checkbox: {
width: '1.2em',
height: '1.2em',
cursor: 'pointer',
},
text: {
fontSize: '1em',
color: {
default: '#fff',
[LIGHT_MODE]: '#333',
},
},
completed: {
textDecoration: 'line-through',
color: '#666',
},
});