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:
- Submit Pull Request - Follow the Contributing Guide
- Update Documentation - Add styling-specific documentation and examples
- Community Feedback - Gather feedback from early users
- Iterate and Improve - Refine based on real-world usage
Related Guides:
- Adding Features โ - General feature addition guide
- Architecture Overview โ - Understanding the system design
- Contributing โ - Contribution guidelines and workflow
Styling Ecosystem: Your styling integration will expand React Kickstart's capabilities and provide developers with more choices for their preferred styling approach.