Extending
Adding Styling Options

Adding a New Styling Option

Complete guide to integrating a new styling solution (e.g., Sass, Emotion, CSS Modules) into React Kickstart. This guide covers everything from dependency management to QA integration.

Reference Implementations: Study the existing Tailwind CSS, styled-components, and plain CSS implementations for patterns and conventions.


๐ŸŽฏ Integration Overview

Adding a new styling option involves several integration points:


๐Ÿ“‹ Complete Integration Checklist

Declare Dependencies and Versions

File: src/builders/dependencies.js

Add your styling dependencies and version information:

// Add Sass dependencies
export const styling = {
  // ...existing styling options
  sass: "^1.77.0",
  sassLoader: "^13.3.0" // if using with webpack-based frameworks
}
 
// Add getter function
export function getSassDependencies(framework) {
  const deps = {
    sass: styling.sass
  }
  
  // Framework-specific dependencies
  if (framework === 'vite') {
    // Vite has built-in Sass support
    return deps
  }
  
  if (framework === 'nextjs') {
    // Next.js also has built-in Sass support
    return deps
  }
  
  return deps
}

Wire Dependency Resolution

File: src/builders/dependency-resolver.js

Update the styling dependency resolution logic:

getStylingDependencies(stylingChoice, framework) {
  switch (stylingChoice) {
    case 'tailwind':
      return this.getTailwindDependencies(framework)
    case 'styled-components':
      return this.getStyledComponentsDependencies(framework)
    case 'css':
      return {} // No additional dependencies for plain CSS
    case 'sass':
      return getSassDependencies(framework)
    case 'emotion':
      return getEmotionDependencies(framework)
    case 'css-modules':
      return getCSSModulesDependencies(framework)
    default:
      return {}
  }
}
 
// Handle dependency placement (dependencies vs devDependencies)
placeStylingDependencies(deps, framework) {
  // Most styling dependencies go in devDependencies
  const devDeps = { ...deps }
  const prodDeps = {}
 
  // Some CSS-in-JS libraries need to be in dependencies for SSR
  if (framework === 'nextjs') {
    if (deps['@emotion/react']) {
      prodDeps['@emotion/react'] = deps['@emotion/react']
      delete devDeps['@emotion/react']
    }
    if (deps['styled-components']) {
      prodDeps['styled-components'] = deps['styled-components']
      delete devDeps['styled-components']
    }
  }
 
  return { dependencies: prodDeps, devDependencies: devDeps }
}

Add to Prompt System

File: src/prompts/steps/styling-step.js

Add your styling option to the choices:

getChoices(previousAnswers) {
  return [
    {
      name: 'Tailwind CSS (Utility-first CSS framework)',
      value: 'tailwind',
      short: 'Tailwind'
    },
    {
      name: 'styled-components (CSS-in-JS)',
      value: 'styled-components',
      short: 'styled-components'
    },
    {
      name: 'Sass (CSS preprocessor with variables and mixins)',
      value: 'sass',
      short: 'Sass'
    },
    {
      name: 'Emotion (Performant CSS-in-JS library)',
      value: 'emotion',
      short: 'Emotion'
    },
    {
      name: 'CSS Modules (Locally scoped CSS)',
      value: 'css-modules',
      short: 'CSS Modules'
    },
    {
      name: 'CSS (Plain CSS files)',
      value: 'css',
      short: 'CSS'
    }
  ]
}

Create Feature Setup Module

File: src/features/styling/your-styling-setup.js

Create a setup module for your styling solution:

// src/features/styling/sass-setup.js
import { CORE_UTILS } from '../../utils/index.js'
import path from 'path'
 
export class SassSetup {
  async setup(projectPath, userChoices) {
    await this.createSassFiles(projectPath, userChoices)
    await this.updateMainCSS(projectPath, userChoices)
  }
  
  async createSassFiles(projectPath, userChoices) {
    // Create main Sass file
    const sassContent = `
// Variables
$primary-color: #3b82f6;
$secondary-color: #64748b;
$success-color: #10b981;
$error-color: #ef4444;
 
// Mixins
@mixin button-style($bg-color) {
  background-color: $bg-color;
  color: white;
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 0.375rem;
  cursor: pointer;
  
  &:hover {
    opacity: 0.9;
  }
}
 
// Base styles
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
 
body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
  line-height: 1.5;
  color: #1f2937;
}
 
// Component styles
.app {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #f3f4f6;
}
 
.container {
  text-align: center;
  padding: 2rem;
  background: white;
  border-radius: 0.5rem;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
 
.title {
  font-size: 2rem;
  font-weight: bold;
  margin-bottom: 1rem;
  color: $primary-color;
}
 
.button {
  @include button-style($primary-color);
  margin: 0.25rem;
}
    `
    
    const sassPath = path.join(projectPath, 'src', 'styles', 'main.scss')
    await CORE_UTILS.ensureDirectory(path.dirname(sassPath))
    await CORE_UTILS.writeFile(sassPath, sassContent.trim())
  }
  
  async updateMainCSS(projectPath, userChoices) {
    // Update main entry to import Sass file
    const mainFile = userChoices.typescript ? 'main.tsx' : 'main.jsx'
    const mainPath = path.join(projectPath, 'src', mainFile)
    
    if (await CORE_UTILS.fileExists(mainPath)) {
      let content = await CORE_UTILS.readFile(mainPath)
      content = content.replace(
        "import './index.css'",
        "import './styles/main.scss'"
      )
      await CORE_UTILS.writeFile(mainPath, content)
    }
  }
}

Integrate with Feature System

File: src/features/styling/index.js

Update the main styling feature integration:

import { TailwindSetup } from "./tailwind-setup.js";
import { StyledComponentsSetup } from "./styled-components-setup.js";
import { SassSetup } from "./sass-setup.js";
import { EmotionSetup } from "./emotion-setup.js";
import { CSSModulesSetup } from "./css-modules-setup.js";
 
export async function setupStyling(projectPath, userChoices) {
  const { styling, framework } = userChoices;
 
  switch (styling) {
    case "tailwind":
      const tailwindSetup = new TailwindSetup();
      await tailwindSetup.setup(projectPath, userChoices);
      break;
 
    case "styled-components":
      const styledSetup = new StyledComponentsSetup();
      await styledSetup.setup(projectPath, userChoices);
      break;
 
    case "sass":
      const sassSetup = new SassSetup();
      await sassSetup.setup(projectPath, userChoices);
      break;
 
    case "emotion":
      const emotionSetup = new EmotionSetup();
      await emotionSetup.setup(projectPath, userChoices);
      break;
 
    case "css-modules":
      const cssModulesSetup = new CSSModulesSetup();
      await cssModulesSetup.setup(projectPath, userChoices);
      break;
 
    case "css":
    default:
      // Plain CSS setup (already handled by base generator)
      break;
  }
}
 
export function getStylingInfo(framework, userChoices) {
  const { styling } = userChoices;
 
  switch (styling) {
    case "sass":
      return {
        cssPath: "src/styles/main.scss",
        importStatement: "import './styles/main.scss'",
      };
 
    case "emotion":
      return {
        cssPath: null, // CSS-in-JS doesn't need CSS files
        importStatement:
          "import { ThemeProvider } from '@emotion/react'\nimport { theme } from './theme'",
      };
 
    case "css-modules":
      return {
        cssPath: "src/App.module.css",
        importStatement: "import styles from './App.module.css'",
      };
 
    default:
      return {
        cssPath: "src/index.css",
        importStatement: "import './index.css'",
      };
  }
}

Update Template System

File: src/templates/frameworks/index.js

Update templates to handle your new styling option:

// Update content generators to handle new styling
class ViteContentGenerator {
  generateAppComponent(userChoices) {
    const { styling, typescript } = userChoices;
 
    let imports = [`import React from 'react'`];
    let className = "";
    let wrapperElement = "div";
 
    switch (styling) {
      case "sass":
        imports.push(`import './styles/main.scss'`);
        className = 'className="app"';
        break;
 
      case "emotion":
        imports.push(`import styled from '@emotion/styled'`);
        imports.push(`import { ThemeProvider } from '@emotion/react'`);
        imports.push(`import { theme } from './theme'`);
        wrapperElement = "AppContainer";
        break;
 
      case "css-modules":
        imports.push(`import styles from './App.module.css'`);
        className = "className={styles.app}";
        break;
 
      case "tailwind":
        imports.push(`import './index.css'`);
        className =
          'className="min-h-screen bg-gray-100 flex items-center justify-center"';
        break;
 
      default:
        imports.push(`import './index.css'`);
        className = 'className="app"';
    }
 
    // Generate component based on styling choice
    const componentContent = this.generateComponentContent(
      styling,
      userChoices
    );
 
    return `${imports.join("\n")}
 
${styling === "emotion" ? this.generateStyledComponents() : ""}
 
function App() {
  return (
    ${styling === "emotion" ? "<ThemeProvider theme={theme}>" : ""}
    <${wrapperElement} ${className}>
      ${componentContent}
    </${wrapperElement}>
    ${styling === "emotion" ? "</ThemeProvider>" : ""}
  )
}
 
export default App`;
  }
 
  generateStyledComponents() {
    return `
const AppContainer = styled.div\`
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: \${props => props.theme.colors.gray[100]};
\`
 
const Container = styled.div\`
  text-align: center;
  padding: \${props => props.theme.spacing.xl};
  background: \${props => props.theme.colors.white};
  border-radius: 0.5rem;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
\`
    `;
  }
}

Add Configuration Generation

File: src/builders/configuration-builder.js

Add configuration file generation for your styling solution:

async generateStylingConfig(projectPath, styling, userChoices) {
  switch (styling) {
    case 'sass':
      // Sass typically doesn't need additional config for Vite/Next.js
      break
 
    case 'emotion':
      await this.generateEmotionConfig(projectPath, userChoices)
      break
 
    case 'css-modules':
      await this.generateCSSModulesConfig(projectPath, userChoices)
      break
 
    // ... other styling options
  }
}
 
async generateEmotionConfig(projectPath, userChoices) {
  if (userChoices.framework === 'nextjs') {
    // Create Next.js Emotion configuration
    const nextConfigPath = path.join(projectPath, 'next.config.js')
    let config = await CORE_UTILS.readFile(nextConfigPath)
 
    // Add Emotion configuration
    config = config.replace(
      'const nextConfig = {',
      `const nextConfig = {
  compiler: {
    emotion: true
  },`
    )
 
    await CORE_UTILS.writeFile(nextConfigPath, config)
  }
}

Update QA System

File: qa-automation/test-matrix-generator.js

Add your styling option to the test matrix:

const CONFIG_OPTIONS = {
  framework: ["vite", "nextjs"],
  typescript: [true, false],
  styling: [
    "tailwind",
    "styled-components",
    "sass",
    "emotion",
    "css-modules",
    "css",
  ],
  // ... other options
};
 
// Add styling-specific validation
const STYLING_VALIDATION = {
  sass: {
    requiredFiles: ["src/styles/main.scss"],
    requiredDependencies: ["sass"],
  },
  emotion: {
    requiredFiles: ["src/theme.ts"],
    requiredDependencies: ["@emotion/react", "@emotion/styled"],
  },
  "css-modules": {
    requiredFiles: ["src/App.module.css"],
    requiredDependencies: [],
  },
};

Update Project Information

File: src/utils/ui/completion.js

Add your styling option to completion messages:

function getStylingDescription(styling) {
  switch (styling) {
    case "sass":
      return "Sass preprocessor with variables and mixins";
    case "emotion":
      return "Emotion CSS-in-JS with theme support";
    case "css-modules":
      return "CSS Modules with locally scoped styles";
    // ... other options
    default:
      return "CSS styling";
  }
}

๐Ÿงช Testing Your Integration

Manual Testing

Test your styling option with different combinations:

# Test with Vite
node bin/react-kickstart.js test-sass --yes --framework vite --styling sass
 
# Test with Next.js
node bin/react-kickstart.js test-emotion --yes --framework nextjs --styling emotion --typescript
 
# Test with TypeScript
node bin/react-kickstart.js test-css-modules --yes --framework vite --styling css-modules --typescript

Automated Testing

Add to the QA automation system:

# Generate updated test matrix
node qa-automation/test-matrix-generator.js
 
# Run tests including your styling option
node qa-automation/test-runner.js critical --full

Validation Checklist

Verify your integration:

  • Dependencies install correctly
  • Configuration files are generated
  • Styling works in development mode
  • Build process succeeds
  • Generated styles are applied correctly
  • TypeScript support works (if enabled)
  • Framework-specific features work
  • QA tests pass

๐ŸŽจ Framework-Specific Considerations

Vite Integration

Vite Support: Vite has excellent built-in support for most CSS preprocessors and CSS-in-JS solutions.

// Vite-specific styling setup
if (framework === "vite") {
  // Vite handles Sass, Less, Stylus out of the box
  if (["sass", "scss", "less", "stylus"].includes(styling)) {
    // Just install the preprocessor, no additional config needed
    return getPreprocessorDependencies(styling);
  }
 
  // CSS-in-JS solutions may need Vite plugin configuration
  if (styling === "emotion") {
    return {
      dependencies: getEmotionDependencies(),
      viteConfig: {
        plugins: ["@emotion/babel-plugin"],
      },
    };
  }
}

Next.js Integration

// Next.js-specific styling setup
if (framework === "nextjs") {
  // Next.js has built-in Sass support
  if (styling === "sass") {
    return { sass: versions.sass };
  }
 
  // CSS-in-JS libraries need special SSR handling
  if (styling === "styled-components") {
    return {
      dependencies: getStyledComponentsDependencies(),
      nextConfig: {
        compiler: {
          styledComponents: true,
        },
      },
    };
  }
}

๐Ÿ”ง Advanced Features

Theme Integration

For styling solutions that support theming:

// Generate theme configuration
async generateThemeConfig(projectPath, styling, userChoices) {
  const themeConfig = {
    colors: {
      primary: '#3b82f6',
      secondary: '#64748b',
      // ... theme colors
    },
    spacing: {
      // ... spacing scale
    },
    typography: {
      // ... typography scale
    }
  }
 
  switch (styling) {
    case 'emotion':
      await this.generateEmotionTheme(projectPath, themeConfig)
      break
    case 'styled-components':
      await this.generateStyledComponentsTheme(projectPath, themeConfig)
      break
    case 'sass':
      await this.generateSassVariables(projectPath, themeConfig)
      break
  }
}

Dark Mode Support

Add dark mode capabilities:

// Generate dark mode configuration
generateDarkModeSupport(styling) {
  switch (styling) {
    case 'tailwind':
      return {
        config: { darkMode: 'class' },
        classes: 'dark:bg-gray-900 dark:text-white'
      }
    case 'css-modules':
      return {
        files: ['src/themes/dark.module.css'],
        implementation: 'CSS custom properties'
      }
    case 'emotion':
      return {
        theme: { mode: 'dark', colors: { ... } },
        implementation: 'Theme provider'
      }
  }
}

๐Ÿ“š Best Practices

Dependency Management

Version Compatibility: Always test your styling solution with the latest versions of React, TypeScript, and the supported frameworks.

  • Keep versions current - Regularly update dependency versions
  • Test compatibility - Ensure your styling works with all framework combinations
  • Handle peer dependencies - Some styling libraries have peer dependency requirements
  • Consider bundle size - Be mindful of the impact on bundle size

Configuration Files

  • Framework-specific configs - Different frameworks may need different configuration approaches
  • TypeScript support - Ensure proper TypeScript definitions and configurations
  • Development vs Production - Consider different settings for development and production builds
  • Error handling - Provide clear error messages for configuration issues

Template Integration

  • Consistent patterns - Follow the same patterns as existing styling integrations
  • Framework variations - Handle differences between Vite and Next.js appropriately
  • Component examples - Provide working examples that demonstrate the styling solution
  • Documentation - Include comments explaining styling-specific patterns

๐Ÿ“š Next Steps

After Integration:

  1. Submit Pull Request - Follow the Contributing Guide
  2. Update Documentation - Add styling-specific documentation and examples
  3. Community Feedback - Gather feedback from early users
  4. Iterate and Improve - Refine based on real-world usage

Related Guides:

Styling Ecosystem: Your styling integration will expand React Kickstart's capabilities and provide developers with more choices for their preferred styling approach.