Ship product at the speed of thought.
/* Describe UI & watch it get built. Tailor-made for your codebase */
Your design, your language, your system.
ion learns from your design system to create components that mesh seamlessly with yours.

"

A form component to set your profile picture, display name, email address, and country. You should be able to select if your account is public or private. The form should include a submit button and validation. [Generated in a neobrutal app]

"

// A form component to set your profile picture, display name, email address, and country. You should be able to select if your account is public or private. The form should include a submit button and validation.

import React, { useState } from 'react';
import { User, Upload } from 'lucide-react';
import Input from './ui/Input';
import Button from './ui/Button';
import Select from './ui/Select';

const ProfileForm = () => {
  const [displayName, setDisplayName] = useState('');
  const [email, setEmail] = useState('');
  const [country, setCountry] = useState('');
  const [isPublic, setIsPublic] = useState(true);
  const [profilePicture, setProfilePicture] = useState(null);
  const [errors, setErrors] = useState({});

  const countries = ['United States', 'Canada', 'United Kingdom', 'Australia', 'Germany', 'France', 'Japan'];

  const handleSubmit = (e) => {
    e.preventDefault();
    const newErrors = {};

    if (!displayName.trim()) newErrors.displayName = 'Display name is required';
    if (!email.trim()) newErrors.email = 'Email is required';
    else if (!/\S+@\S+\.\S+/.test(email)) newErrors.email = 'Email is invalid';
    if (!country) newErrors.country = 'Country is required';

    if (Object.keys(newErrors).length === 0) {
      // Submit form data
      console.log('Form submitted:', { displayName, email, country, isPublic, profilePicture });
    } else {
      setErrors(newErrors);
    }
  };

  const handleFileChange = (e) => {
    const file = e.target.files[0];
    if (file) {
      setProfilePicture(URL.createObjectURL(file));
    }
  };

  return (
    <div className="w-[400px] max-w-md mx-auto mt-10 p-6 border-2 border-border dark:border-darkBorder rounded-base bg-bg dark:bg-darkBg shadow-light dark:shadow-dark">
      <h2 className="text-2xl font-heading mb-6 text-text dark:text-darkText">Profile Settings</h2>
      <form onSubmit={handleSubmit}>
        <div className="mb-6">
          <div className="w-32 h-32 mx-auto">
            <div className="relative h-full">
              {profilePicture ? (
                <img src={profilePicture} alt="Profile" className="w-full h-full object-cover rounded-full" />
              ) : (
                <div className="w-full h-full bg-main rounded-full flex items-center justify-center">
                  <User className="w-12 h-12 text-text dark:text-darkText" />
                </div>
              )}
              <label htmlFor="profile-picture" className="absolute bottom-0 right-0 bg-main p-2 rounded-full cursor-pointer">
                <Upload className="w-4 h-4 text-text dark:text-darkText" />
              </label>
              <input
                id="profile-picture"
                type="file"
                accept="image/*"
                onChange={handleFileChange}
                className="hidden"
              />
            </div>
          </div>
        </div>
        <div className="mb-4">
          <label htmlFor="display-name" className="block mb-2 font-base text-text dark:text-darkText">Display Name</label>
          <Input
            className="w-full"
            value={displayName}
            setValue={setDisplayName}
            placeholder="Enter your display name"
          />
          {errors.displayName && <p className="text-red-500 text-sm mt-1">{errors.displayName}</p>}
        </div>
        <div className="mb-4">
          <label htmlFor="email" className="block mb-2 font-base text-text dark:text-darkText">Email Address</label>
          <Input
            className="w-full"
            value={email}
            setValue={setEmail}
            placeholder="Enter your email address"
          />
          {errors.email && <p className="text-red-500 text-sm mt-1">{errors.email}</p>}
        </div>
        <div className="mb-4">
          <label htmlFor="country" className="block mb-2 font-base text-text dark:text-darkText">Country</label>
          <Select
            items={countries}
            onChange={(selectedCountry) => setCountry(selectedCountry)}
            value={country}
          />
          {errors.country && <p className="text-red-500 text-sm mt-1">{errors.country}</p>}
        </div>
        <div className="mb-6">
          <label className="flex items-center cursor-pointer">
            <div className="relative">
              <input
                type="checkbox"
                className="sr-only"
                checked={isPublic}
                onChange={() => setIsPublic(!isPublic)}
              />
              <div className={`block w-14 h-8 rounded-full ${isPublic ? 'bg-main' : 'bg-gray-600'}`}></div>
              <div className={`dot absolute left-1 top-1 bg-white w-6 h-6 rounded-full transition ${isPublic ? 'transform translate-x-6' : ''}`}></div>
            </div>
            <div className="ml-3 text-text dark:text-darkText font-base">
              {isPublic ? 'Public Account' : 'Private Account'}
            </div>
          </label>
        </div>
        <Button
          onClick={handleSubmit}
          className="w-full justify-center"
        >
          Save Profile
        </Button>
      </form>
    </div>
  );
};

export default ProfileForm;
// Weather card that displays weather and a suggestion of what to wear at a zip code

import React, { useState } from 'react';
import { CloudSun, Thermometer, Wind, Drop, MapPin } from '@phosphor-icons/react/dist/ssr';
import Button from '@/components/ion/Button';
import Input from '@/components/ion/Input';
import { motion } from "framer-motion";
import Divider from './ion/Divider';

const sampleWeatherData = {
    city: 'Toronto',
    temperature: 20,
    condition: 'Sunny',
    feelsLike: 22,
    windSpeed: 10,
    humidity: 50,
};

const WeatherCard = () => {
  const [zipCode, setZipCode] = useState('');
  const [weather, setWeather] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');

  const fetchWeather = async () => {
    setLoading(true);
    setError('');
    try {
      // Replace with actual API call
      // const response = await fetch(`https://api.example.com/weather/${zipCode}`);
      // const data = await response.json();
      // setWeather(data);
      setWeather(sampleWeatherData);
    } catch (err) {
      setError('Failed to fetch weather data');
    } finally {
      setLoading(false);
    }
  };

  const getClothingSuggestion = (temperature) => {
    if (temperature > 25) return 'Light clothing, shorts, and t-shirt';
    if (temperature > 15) return 'Light jacket or sweater';
    if (temperature > 5) return 'Warm coat and layers';
    return 'Heavy winter coat, scarf, and gloves';
  };

  return (
    <div className="bg-background w-[400px] flex flex-col gap-5 p-6 rounded-radius-md shadow-medium">
      <div className="flex items-center gap-2">
        <Input
          placeholder="Enter ZIP code"
          value={zipCode}
          onChange={(e) => setZipCode(e.target.value)}
          className="flex-grow"
          iconLeading={<MapPin size={16} weight="bold" />}
        />
        <Button
          variant="filled"
          color="primary"
          size="md"
          onClick={fetchWeather}
          disabled={loading || !zipCode}
        >
          {loading ? 'Loading...' : 'Get Weather'}
        </Button>
      </div>

      {error && <div className="text-danger text-sm">{error}</div>}

      {weather && (
        <motion.div
          initial={{ opacity: 0, y: 20 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.3 }}
          className="flex flex-col gap-4"
        >
          <div className="flex items-center justify-between">
            <div className="text-2xl font-semibold text-foreground">{weather.city}</div>
            <div className="text-4xl font-bold text-primary">{weather.temperature}°C</div>
          </div>

          <div className="flex justify-between text-secondary">
            <div className="flex items-center gap-2">
              <CloudSun size={20} />
              <span>{weather.condition}</span>
            </div>
            <div className="flex items-center gap-2">
              <Thermometer size={20} />
              <span>{weather.feelsLike}°C</span>
            </div>
          </div>

          <div className="flex justify-between text-secondary">
            <div className="flex items-center gap-2">
              <Wind size={20} />
              <span>{weather.windSpeed} km/h</span>
            </div>
            <div className="flex items-center gap-2">
              <Drop size={20} />
              <span>{weather.humidity}%</span>
            </div>
          </div>

          <Divider />

          <div className="text-foreground">
            <div className="font-semibold mb-2">What to Wear:</div>
            <div>{getClothingSuggestion(weather.temperature)}</div>
          </div>
        </motion.div>
      )}
    </div>
  );
};

export default WeatherCard;
// A card that displays the relevant statistics of a basketball player, their photo, their current team, age, and origin. You should be able to click the card to expand more info.

import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { ChevronDown, ChevronUp } from 'lucide-react';

const PlayerCard = ({ player }) => {
  const [isExpanded, setIsExpanded] = useState(false);

  const toggleExpand = () => {
    setIsExpanded(!isExpanded);
  };

  return (
    <motion.div 
      className="glass dark:glass-dark p-6 rounded-xl shadow-lg text-white max-w-md mx-auto cursor-pointer"
      onClick={toggleExpand}
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.5 }}
    >
      <div className="flex items-center mb-4">
        <img src={player.photo} alt={player.name} className="w-24 h-24 rounded-full mr-4 object-cover" />
        <div>
          <h2 className="text-2xl font-semibold">{player.name}</h2>
          <p className="text-gray-300">{player.team}</p>
        </div>
      </div>
      
      <div className="grid grid-cols-2 gap-4 mb-4">
        <div>
          <p className="text-gray-400">Age</p>
          <p className="text-xl">{player.age}</p>
        </div>
        <div>
          <p className="text-gray-400">Origin</p>
          <p className="text-xl">{player.origin}</p>
        </div>
      </div>

      <div className="grid grid-cols-3 gap-4 mb-4">
        <div>
          <p className="text-gray-400">PPG</p>
          <p className="text-xl">{player.stats.ppg}</p>
        </div>
        <div>
          <p className="text-gray-400">RPG</p>
          <p className="text-xl">{player.stats.rpg}</p>
        </div>
        <div>
          <p className="text-gray-400">APG</p>
          <p className="text-xl">{player.stats.apg}</p>
        </div>
      </div>

      <AnimatePresence>
        {isExpanded && (
          <motion.div
            initial={{ opacity: 0, height: 0 }}
            animate={{ opacity: 1, height: 'auto' }}
            exit={{ opacity: 0, height: 0 }}
            transition={{ duration: 0.3 }}
          >
            <div className="mt-4 pt-4 border-t border-gray-600">
              <h3 className="text-xl font-semibold mb-2">Additional Info</h3>
              <p className="text-gray-300 mb-2">Height: {player.height}</p>
              <p className="text-gray-300 mb-2">Weight: {player.weight}</p>
              <p className="text-gray-300 mb-2">Position: {player.position}</p>
              <p className="text-gray-300">Draft: {player.draft}</p>
            </div>
          </motion.div>
        )}
      </AnimatePresence>

      <div className="flex justify-center mt-4">
        {isExpanded ? (
          <ChevronUp className="text-gray-400" />
        ) : (
          <ChevronDown className="text-gray-400" />
        )}
      </div>
    </motion.div>
  );
};

const BasketballPlayerStats = () => {
  const playerData = {
    name: "LeBron James",
    photo: "https://hoopshype.com/wp-content/uploads/sites/92/2024/02/i_54_cf_2e_lebron-james.png?w=1000&h=600&crop=1",
    team: "Los Angeles Lakers",
    age: 36,
    origin: "Akron, Ohio",
    stats: {
      ppg: 25.0,
      rpg: 7.7,
      apg: 7.8
    },
    height: "6'9\"",
    weight: "250 lbs",
    position: "Small Forward",
    draft: "2003 Round 1 Pick 1"
  };

  return (
    <div className="p-4">
      <PlayerCard player={playerData} />
    </div>
  );
};

export default BasketballPlayerStats;
// A kanban board component with 3 columns, "backlog", "in progress", and "done". Each column should have task cards with a title of the task and other information. You should be able to drag and drop from one column to another.

import React, { useState } from 'react';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';

const initialTasks = {
  backlog: [
    { id: 'task-1', title: 'Create login page', description: 'Implement user authentication' },
    { id: 'task-2', title: 'Design database schema', description: 'Plan out data structure' },
  ],
  inProgress: [
    { id: 'task-3', title: 'Develop API endpoints', description: 'Create RESTful API for the application' },
  ],
  done: [
    { id: 'task-4', title: 'Set up project repository', description: 'Initialize Git repo and invite team members' },
  ],
};

const KanbanBoard = () => {
  const [tasks, setTasks] = useState(initialTasks);

  const onDragEnd = (result) => {
    const { source, destination } = result;
    if (!destination) return;

    const sourceColumn = tasks[source.droppableId];
    const destColumn = tasks[destination.droppableId];
    const [removed] = sourceColumn.splice(source.index, 1);
    destColumn.splice(destination.index, 0, removed);

    setTasks({
      ...tasks,
      [source.droppableId]: sourceColumn,
      [destination.droppableId]: destColumn,
    });
  };

  return (
    <div className="nes-container with-title is-dark p-4 max-w-6xl mx-auto mt-10">
      <p className="title">Kanban Board</p>
      <DragDropContext onDragEnd={onDragEnd}>
        <div className="flex justify-between space-x-4">
          {Object.keys(tasks).map((columnId) => (
            <div key={columnId} className="flex-1">
              <h2 className="text-lg font-bold mb-2 capitalize">{columnId}</h2>
              <Droppable droppableId={columnId}>
                {(provided) => (
                  <div
                    {...provided.droppableProps}
                    ref={provided.innerRef}
                    className="nes-container is-rounded min-h-[300px]"
                  >
                    {tasks[columnId].map((task, index) => (
                      <Draggable key={task.id} draggableId={task.id} index={index}>
                        {(provided) => (
                          <div
                            ref={provided.innerRef}
                            {...provided.draggableProps}
                            {...provided.dragHandleProps}
                            className="nes-container is-rounded mb-2 bg-white text-black"
                          >
                            <h3 className="font-bold">{task.title}</h3>
                            <p className="text-sm">{task.description}</p>
                          </div>
                        )}
                      </Draggable>
                    ))}
                    {provided.placeholder}
                  </div>
                )}
              </Droppable>
            </div>
          ))}
        </div>
      </DragDropContext>
    </div>
  );
};

export default KanbanBoard;
industry leading code generation.
A mix of custom models and off-the-shelf LLMs generate code that understands your use case.
Understands your libraries
ion will learn how to use the libraries you are importing, and use them to accomplish tasks like charting and drag+drop.
Built for teams
ion learns from your existing code to write components indistinguishable from what your engineers write. No more spaghetti code.
Logic and stub functions
ion generates complicated components with ease. Front end will be fully hooked up and ready to ship with animations, state, and even fetching. Just hook up your api and ship!
Scale with design to code.
As you scale your team, ion can automatically turn your Figma designs into clean react components.
Our shipping cadence has been unleashed since we started using Ion. And surprisingly, it's improved the quality of our codebase and our final product. That's because we can spend more time thinking about design and architecture and less time on UI and CSS. It's hard for me to imagine going back.
Agree Ahmed
Cofounder, NUMI (YC W20)
Get Started today, for free!
Supercharge your front end process
Start Free Trial