Skip to main content

Advanced Usage

This guide covers advanced patterns and techniques for using Tailor effectively in your React applications.

Composing Components​

You can compose multiple styled components to create complex UI elements. Here's an example of a form component:

interface InputProps {
error?: boolean;
disabled?: boolean;
}

const Input = craft('input')({
base: 'w-full px-4 py-2 rounded border transition-colors',
dynamic: {
'border-gray-300 focus:border-blue-500': (props) => !props.error,
'border-red-500 focus:border-red-600': (props) => props.error,
'bg-gray-100 cursor-not-allowed': (props) => props.disabled,
},
});

interface LabelProps {
required?: boolean;
}

const Label = craft('label')({
base: 'block text-sm font-medium text-gray-700 mb-1',
dynamic: {
'after:content-["*"] after:text-red-500 after:ml-1': (props) => props.required,
},
});

interface FormFieldProps {
label: string;
required?: boolean;
error?: string;
disabled?: boolean;
}

const FormField: React.FC<FormFieldProps> = ({
label,
required,
error,
disabled,
...props
}) => (
<div className="mb-4">
<Label required={required}>{label}</Label>
<Input error={!!error} disabled={disabled} {...props} />
{error && <p className="mt-1 text-sm text-red-500">{error}</p>}
</div>
);

// Usage
<FormField
label="Email"
required
type="email"
error="Please enter a valid email address"
/>

Theme Integration​

You can integrate Tailor with your application's theme system. Here's an example using a theme context:

interface Theme {
colors: {
primary: string;
secondary: string;
background: string;
text: string;
};
spacing: {
sm: string;
md: string;
lg: string;
};
}

const ThemeContext = React.createContext<Theme>(defaultTheme);

const useTheme = () => React.useContext(ThemeContext);

const ThemedButton = craft(Button)({
base: 'rounded font-medium transition-colors',
dynamic: (props) => {
const theme = useTheme();
return {
[`bg-${theme.colors.primary} text-white`]: () => true,
[`hover:bg-${theme.colors.secondary}`]: () => true,
[`p-${theme.spacing.md}`]: () => true,
};
},
});

Custom Hooks​

Create custom hooks to encapsulate common styling logic:

interface UseButtonStylesProps {
variant: 'primary' | 'secondary' | 'danger';
size: 'sm' | 'md' | 'lg';
disabled?: boolean;
}

const useButtonStyles = ({ variant, size, disabled }: UseButtonStylesProps) => ({
base: 'rounded font-medium transition-colors',
dynamic: {
// Variant styles
'bg-blue-500 text-white hover:bg-blue-600': () => variant === 'primary',
'bg-gray-500 text-white hover:bg-gray-600': () => variant === 'secondary',
'bg-red-500 text-white hover:bg-red-600': () => variant === 'danger',

// Size styles
'px-2 py-1 text-sm': () => size === 'sm',
'px-4 py-2 text-base': () => size === 'md',
'px-6 py-3 text-lg': () => size === 'lg',

// Disabled state
'opacity-50 cursor-not-allowed': () => disabled,
},
});

const Button = craft('button')((props) => useButtonStyles(props));

Performance Optimization​

Memoization​

Use React's useMemo to memoize style configurations:

const StyledComponent = React.memo(({ variant, size, ...props }) => {
const styles = React.useMemo(
() => ({
base: 'rounded font-medium',
dynamic: {
[`bg-${variant}-500`]: () => true,
[`text-${size}`]: () => true,
},
}),
[variant, size]
);

return <Component styles={styles} {...props} />;
});

Style Deduplication​

Tailor automatically deduplicates classes, but you can optimize further by extracting common styles:

const commonStyles = {
base: 'rounded font-medium transition-colors',
responsive: {
sm: 'text-sm',
md: 'text-base',
lg: 'text-lg',
},
};

const PrimaryButton = craft(Button)({
...commonStyles,
dynamic: {
'bg-blue-500 text-white hover:bg-blue-600': () => true,
},
});

const SecondaryButton = craft(Button)({
...commonStyles,
dynamic: {
'bg-gray-500 text-white hover:bg-gray-600': () => true,
},
});

Testing​

Unit Testing​

Test your styled components using Jest and React Testing Library:

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

describe('StyledButton', () => {
it('applies correct styles based on props', () => {
const { rerender } = render(<StyledButton>Click me</StyledButton>);

// Check base styles
expect(screen.getByRole('button')).toHaveClass('rounded font-medium');

// Check variant styles
rerender(<StyledButton variant="primary">Click me</StyledButton>);
expect(screen.getByRole('button')).toHaveClass('bg-blue-500');

// Check disabled state
rerender(<StyledButton disabled>Click me</StyledButton>);
expect(screen.getByRole('button')).toHaveClass('opacity-50');
});

it('handles user interactions correctly', async () => {
const handleClick = jest.fn();
render(<StyledButton onClick={handleClick}>Click me</StyledButton>);

await userEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalled();
});
});

Visual Testing​

Use tools like Storybook to test your components visually:

// Button.stories.tsx
export default {
title: 'Components/Button',
component: StyledButton,
};

export const Primary = {
args: {
variant: 'primary',
children: 'Primary Button',
},
};

export const Secondary = {
args: {
variant: 'secondary',
children: 'Secondary Button',
},
};

export const Disabled = {
args: {
disabled: true,
children: 'Disabled Button',
},
};

Error Handling​

Implement proper error boundaries and validation:

class StyledComponentErrorBoundary extends React.Component {
state = { hasError: false };

static getDerivedStateFromError() {
return { hasError: true };
}

render() {
if (this.state.hasError) {
return <div className="p-4 bg-red-100 text-red-700 rounded">Something went wrong</div>;
}
return this.props.children;
}
}

const SafeStyledComponent = (props) => (
<StyledComponentErrorBoundary>
<StyledComponent {...props} />
</StyledComponentErrorBoundary>
);

Accessibility​

Ensure your styled components are accessible:

const AccessibleButton = craft(Button)({
base: 'rounded font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2',
dynamic: {
'bg-blue-500 text-white hover:bg-blue-600': () => true,
'aria-disabled:opacity-50 aria-disabled:cursor-not-allowed': (props) => props.disabled,
},
});

// Usage
<AccessibleButton
aria-label="Submit form"
role="button"
tabIndex={0}
onKeyPress={(e) => e.key === 'Enter' && handleClick()}
>
Submit
</AccessibleButton>

Migration Guide​

If you're migrating from other styling solutions, here are some tips:

  1. From CSS Modules:

    • Convert CSS classes to Tailwind utility classes
    • Use the nested property for complex selectors
    • Replace CSS variables with Tailwind's theme system
  2. From Styled Components:

    • Replace styled-component syntax with Tailor's craft function
    • Convert CSS-in-JS to Tailwind classes
    • Use the dynamic property for prop-based styles
  3. From CSS-in-JS Libraries:

    • Move styles to Tailwind classes
    • Use the base property for static styles
    • Leverage the dynamic property for conditional styles

Remember to:

  • Test thoroughly after migration
  • Update your build configuration
  • Update your documentation
  • Train your team on the new patterns