Skip to content

Idea for docs: Add support for playground persistence #832

Open
@zaydek

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',
  },
});
Screenshot 2025-01-03 at 8 42 45 PM

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions