Customising
Advanced customization for Ask0's UI including themes, layouts, and behavior. Tailor the AI assistant experience to your needs.
Take full control of Ask0's appearance and behavior with advanced customization options. From custom CSS to component overrides, create a completely unique experience.
Custom CSS
CSS Variables
Override CSS custom properties:
/* Global CSS variables */
:root {
/* Colors */
--ask0-primary: #0066CC;
--ask0-primary-foreground: #FFFFFF;
--ask0-secondary: #F0F4F8;
--ask0-secondary-foreground: #000000;
--ask0-accent: #00AA55;
--ask0-muted: #F5F5F5;
--ask0-border: #E0E0E0;
/* Typography */
--ask0-font-sans: 'Inter', system-ui, sans-serif;
--ask0-font-mono: 'SF Mono', Monaco, monospace;
--ask0-font-size-xs: 0.75rem;
--ask0-font-size-sm: 0.875rem;
--ask0-font-size-base: 1rem;
--ask0-font-size-lg: 1.125rem;
--ask0-font-size-xl: 1.25rem;
/* Spacing */
--ask0-radius: 0.5rem;
--ask0-radius-sm: 0.25rem;
--ask0-radius-lg: 1rem;
--ask0-spacing-xs: 0.25rem;
--ask0-spacing-sm: 0.5rem;
--ask0-spacing-md: 1rem;
--ask0-spacing-lg: 1.5rem;
--ask0-spacing-xl: 2rem;
/* Animations */
--ask0-animation-duration: 200ms;
--ask0-animation-timing: ease-in-out;
/* Shadows */
--ask0-shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
--ask0-shadow-md: 0 4px 6px rgba(0,0,0,0.1);
--ask0-shadow-lg: 0 10px 15px rgba(0,0,0,0.1);
}
/* Dark mode */
[data-theme="dark"] {
--ask0-primary: #4DA6FF;
--ask0-background: #1A1A1A;
--ask0-foreground: #FFFFFF;
--ask0-border: #333333;
}Component Styling
Target specific components:
/* Widget container */
.ask0-widget {
border: 2px solid var(--ask0-border);
box-shadow: var(--ask0-shadow-lg);
backdrop-filter: blur(10px);
}
/* Header customization */
.ask0-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
font-weight: 600;
}
/* Message bubbles */
.ask0-message--user {
background: var(--ask0-primary);
color: var(--ask0-primary-foreground);
border-radius: 18px 18px 4px 18px;
}
.ask0-message--assistant {
background: var(--ask0-muted);
color: var(--ask0-foreground);
border-radius: 18px 18px 18px 4px;
}
/* Input field */
.ask0-input {
border: 2px solid transparent;
background: var(--ask0-muted);
transition: all 0.3s ease;
}
.ask0-input:focus {
border-color: var(--ask0-primary);
background: white;
box-shadow: 0 0 0 3px rgba(0,102,204,0.1);
}
/* Custom animations */
@keyframes slideInUp {
from {
transform: translateY(100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.ask0-widget--opening {
animation: slideInUp 0.3s ease-out;
}Component Overrides
Custom React Components
Replace default components:
import { Ask0Provider, Ask0Widget } from '@ask0/react';
// Custom header component
const CustomHeader = ({ onClose, onMinimize }) => (
<div className="custom-header">
<img src="/logo.png" alt="Logo" />
<h2>AI Assistant</h2>
<button onClick={onClose}>×</button>
</div>
);
// Custom message component
const CustomMessage = ({ message, isUser }) => (
<div className={`custom-message ${isUser ? 'user' : 'assistant'}`}>
{message.content}
{message.sources && (
<div className="sources">
{message.sources.map(source => (
<a href={source.url}>{source.title}</a>
))}
</div>
)}
</div>
);
// Custom input component
const CustomInput = ({ onSend }) => {
const [value, setValue] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
onSend(value);
setValue('');
};
return (
<form onSubmit={handleSubmit} className="custom-input">
<input
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Ask me anything..."
/>
<button type="submit">Send</button>
</form>
);
};
// Use custom components
function App() {
return (
<Ask0Provider>
<Ask0Widget
components={{
header: CustomHeader,
message: CustomMessage,
input: CustomInput
}}
/>
</Ask0Provider>
);
}Template Overrides
Override HTML templates:
window.ask0.templates = {
// Message template
message: (data) => `
<div class="message ${data.isUser ? 'user' : 'assistant'}">
<img src="${data.avatar}" alt="Avatar" />
<div class="content">
<div class="text">${data.content}</div>
<div class="meta">
<span class="time">${data.time}</span>
${data.sources ? `<span class="sources">${data.sources.length} sources</span>` : ''}
</div>
</div>
</div>
`,
// Input template
input: () => `
<div class="input-wrapper">
<textarea placeholder="Type here..."></textarea>
<button class="send">→</button>
<button class="voice">🎤</button>
</div>
`,
// Loading template
loading: () => `
<div class="loading">
<div class="dots">
<span></span>
<span></span>
<span></span>
</div>
<span class="text">Thinking...</span>
</div>
`
};Layout Customization
Grid Layout
Create custom layouts:
/* Custom grid layout */
.ask0-widget {
display: grid;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header"
"messages"
"input";
gap: 0;
}
.ask0-header {
grid-area: header;
position: sticky;
top: 0;
z-index: 10;
}
.ask0-messages {
grid-area: messages;
overflow-y: auto;
padding: var(--ask0-spacing-md);
}
.ask0-input-area {
grid-area: input;
position: sticky;
bottom: 0;
background: white;
border-top: 1px solid var(--ask0-border);
}
/* Sidebar layout variant */
.ask0-widget--sidebar {
grid-template-columns: 200px 1fr;
grid-template-areas:
"sidebar header"
"sidebar messages"
"sidebar input";
}
.ask0-sidebar {
grid-area: sidebar;
background: var(--ask0-muted);
padding: var(--ask0-spacing-md);
}Responsive Adjustments
Adapt to different screen sizes:
/* Mobile adjustments */
@media (max-width: 768px) {
.ask0-widget {
position: fixed !important;
inset: 0 !important;
width: 100% !important;
height: 100% !important;
border-radius: 0 !important;
}
.ask0-messages {
padding: var(--ask0-spacing-sm);
}
.ask0-message {
max-width: 85% !important;
}
}
/* Tablet adjustments */
@media (min-width: 768px) and (max-width: 1024px) {
.ask0-widget {
width: 450px;
height: 650px;
}
}
/* Large screens */
@media (min-width: 1440px) {
.ask0-widget {
width: 420px;
height: 680px;
}
.ask0-message {
font-size: 1.05rem;
}
}Behavior Customization
Custom Interactions
Define custom user interactions:
// Custom keyboard shortcuts
window.ask0.shortcuts = {
'cmd+k': () => window.ask0.open(),
'cmd+/': () => window.ask0.toggle(),
'escape': () => window.ask0.close(),
'cmd+enter': () => window.ask0.send(),
'up': () => window.ask0.previousMessage(),
'down': () => window.ask0.nextMessage()
};
// Custom gestures (mobile)
window.ask0.gestures = {
swipeDown: () => window.ask0.minimize(),
swipeLeft: () => window.ask0.close(),
pinch: () => window.ask0.toggleFullscreen(),
doubleTap: () => window.ask0.clearMessages()
};
// Custom triggers
window.ask0.triggers = {
// Click trigger
click: {
selector: '.help-button, .support-link',
action: 'open'
},
// Hover trigger
hover: {
selector: '.tooltip',
delay: 1000,
action: 'showHint'
},
// Scroll trigger
scroll: {
percent: 75,
action: 'suggest'
}
};State Management
Manage widget state:
// Custom state manager
class Ask0StateManager {
constructor() {
this.state = {
isOpen: false,
messages: [],
user: null,
context: {}
};
}
// State persistence
persist() {
localStorage.setItem('ask0_state', JSON.stringify(this.state));
}
restore() {
const saved = localStorage.getItem('ask0_state');
if (saved) {
this.state = JSON.parse(saved);
}
}
// State hooks
beforeOpen() {
// Custom logic before opening
analytics.track('Widget Opening');
}
afterClose() {
// Custom logic after closing
this.persist();
}
onMessage(message) {
// Custom message handling
this.state.messages.push(message);
this.persist();
}
}
// Apply custom state manager
window.ask0.stateManager = new Ask0StateManager();Advanced Theming
Dynamic Themes
Change themes programmatically:
// Theme manager
const themeManager = {
themes: {
light: {
primary: '#0066CC',
background: '#FFFFFF',
foreground: '#000000'
},
dark: {
primary: '#4DA6FF',
background: '#1A1A1A',
foreground: '#FFFFFF'
},
custom: {
primary: '#FF6B6B',
background: '#FFF5F5',
foreground: '#2D3748'
}
},
apply(themeName) {
const theme = this.themes[themeName];
Object.entries(theme).forEach(([key, value]) => {
document.documentElement.style.setProperty(`--ask0-${key}`, value);
});
window.ask0.theme = themeName;
},
// Auto theme based on time
autoTheme() {
const hour = new Date().getHours();
const theme = hour >= 6 && hour < 18 ? 'light' : 'dark';
this.apply(theme);
},
// Match system theme
matchSystem() {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
this.apply(prefersDark ? 'dark' : 'light');
}
};Theme Builder
Create themes visually:
// Theme builder utility
class Ask0ThemeBuilder {
constructor() {
this.theme = {};
}
setColors(colors) {
this.theme.colors = colors;
return this;
}
setTypography(typography) {
this.theme.typography = typography;
return this;
}
setSpacing(spacing) {
this.theme.spacing = spacing;
return this;
}
setShadows(shadows) {
this.theme.shadows = shadows;
return this;
}
build() {
return this.theme;
}
export() {
return JSON.stringify(this.theme, null, 2);
}
import(themeString) {
this.theme = JSON.parse(themeString);
return this;
}
}
// Create custom theme
const customTheme = new Ask0ThemeBuilder()
.setColors({
primary: '#FF6B6B',
secondary: '#4ECDC4'
})
.setTypography({
fontFamily: 'Helvetica Neue',
fontSize: '16px'
})
.build();
window.ask0.setTheme(customTheme);Plugin System
Creating Plugins
Extend functionality with plugins:
// Plugin template
class Ask0Plugin {
constructor(name, options = {}) {
this.name = name;
this.options = options;
}
// Lifecycle hooks
onInit(widget) {
console.log(`${this.name} plugin initialized`);
}
onOpen(widget) {
// Widget opened
}
onMessage(message, widget) {
// Message sent/received
}
onClose(widget) {
// Widget closed
}
// Custom methods
enhance(widget) {
// Add custom functionality
}
}
// Example: Analytics plugin
class AnalyticsPlugin extends Ask0Plugin {
constructor(tracker) {
super('analytics');
this.tracker = tracker;
}
onOpen() {
this.tracker.track('Widget Opened');
}
onMessage(message) {
this.tracker.track('Message Sent', {
type: message.isUser ? 'user' : 'assistant',
length: message.content.length
});
}
}
// Register plugins
window.ask0.registerPlugin(new AnalyticsPlugin(analytics));Available Plugins
Community plugins:
// Emoji picker plugin
import EmojiPlugin from '@ask0/plugin-emoji';
// Voice input plugin
import VoicePlugin from '@ask0/plugin-voice';
// Translation plugin
import TranslatePlugin from '@ask0/plugin-translate';
// Code highlighting plugin
import CodePlugin from '@ask0/plugin-code';
// Register multiple plugins
window.ask0.use([
new EmojiPlugin(),
new VoicePlugin({ lang: 'en-US' }),
new TranslatePlugin({ target: 'auto' }),
new CodePlugin({ theme: 'github-dark' })
]);Performance Optimization
Lazy Loading
Load components on demand:
// Lazy load configuration
const lazyConfig = {
// Load widget on first interaction
widget: {
trigger: 'interaction', // 'immediate', 'interaction', 'idle'
preload: true
},
// Load features as needed
features: {
emoji: 'onDemand',
voice: 'onDemand',
fileUpload: 'onDemand',
codeHighlight: 'onDemand'
},
// Resource loading
resources: {
images: 'lazy',
fonts: 'swap',
styles: 'async'
}
};
// Apply lazy loading
window.ask0.lazyLoad = lazyConfig;Caching Strategy
Optimize with caching:
// Cache configuration
const cacheConfig = {
// Message cache
messages: {
enabled: true,
maxSize: 100,
ttl: 3600 // seconds
},
// Response cache
responses: {
enabled: true,
maxSize: 50,
ttl: 1800
},
// Asset cache
assets: {
enabled: true,
strategies: {
images: 'cacheFirst',
scripts: 'networkFirst',
styles: 'staleWhileRevalidate'
}
},
// Storage
storage: 'localStorage', // 'localStorage', 'sessionStorage', 'indexedDB'
compress: true
};Best Practices
Customization Best Practices:
- Keep customizations modular and maintainable
- Test across different browsers and devices
- Use CSS variables for consistent theming
- Optimize for performance
- Ensure accessibility isn't compromised
- Document custom configurations
- Version control your customizations