
Research
/Security News
Fake imToken Chrome Extension Steals Seed Phrases via Phishing Redirects
Mixed-script homoglyphs and a lookalike domain mimic imToken’s import flow to capture mnemonics and private keys.
@instructure/platform-widget-dashboard
Advanced tools
A flexible, extensible dashboard system for Canvas LMS that allows students to customize their learning experience with various widgets.
A flexible, extensible dashboard system for Canvas LMS that allows students to customize their learning experience with various widgets.
The widget dashboard provides a 3-column grid layout where different widgets can be positioned and sized according to configuration. The system is designed to be extensible, with a template-based architecture that makes creating new widgets straightforward.
Create the directory structure for your new widget:
mkdir -p ui/features/widget_dashboard/react/components/widgets/MyWidget/__tests__
Create the main widget component:
// ui/features/widget_dashboard/react/components/widgets/MyWidget/MyWidget.tsx
import React, {useState, useEffect} from 'react'
import {useScope as createI18nScope} from '@canvas/i18n'
import {Button} from '@instructure/ui-buttons'
import {Text} from '@instructure/ui-text'
import TemplateWidget from '../TemplateWidget'
import type {BaseWidgetProps} from '../../../types'
const I18n = createI18nScope('widget_dashboard')
const MyWidget: React.FC<BaseWidgetProps> = ({widget, isLoading, error, onRetry}) => {
const [data, setData] = useState<string>('Loading...')
useEffect(() => {
// Your data fetching logic here
setTimeout(() => {
setData('Widget data loaded!')
}, 1000)
}, [])
const handleAction = () => {
console.log('Widget action clicked')
}
return (
<TemplateWidget
widget={widget}
title="Custom Widget Title" // Optional: Override widget.title
isLoading={isLoading}
error={error}
onRetry={onRetry}
showHeader={true} // Optional: Show/hide header (default: true)
headerActions={
<Button size="small" variant="ghost">
{I18n.t('Settings')}
</Button>
}
actions={
<Button onClick={handleAction} size="small">
{I18n.t('Widget Action')}
</Button>
}
>
<div>
<Text size="medium">{data}</Text>
<Text size="small" color="secondary">
{I18n.t('This is my custom widget content')}
</Text>
</div>
</TemplateWidget>
)
}
export default MyWidget
Create an index file for clean exports:
// ui/features/widget_dashboard/react/components/widgets/MyWidget/index.ts
export {default} from './MyWidget'
The TemplateWidget component accepts the following props to provide a consistent widget experience:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
widget | Widget | ✅ Yes | - | Widget configuration object containing id, type, position, size, and title |
children | React.ReactNode | ✅ Yes | - | The main content of your widget |
title | string | ❌ No | widget.title | Override the widget title. If not provided, uses widget.title |
isLoading | boolean | ❌ No | false | Shows loading spinner when true, hides children content |
error | string | null | ❌ No | null | Error message to display. When set, shows error state and hides children |
onRetry | () => void | ❌ No | undefined | Callback for retry button. Only shows retry button if provided and error exists |
showHeader | boolean | ❌ No | true | Whether to show the widget header with title |
headerActions | React.ReactNode | ❌ No | undefined | Additional actions to display in the header (e.g., settings, info buttons) |
actions | React.ReactNode | ❌ No | undefined | Action buttons to display at the bottom of the widget |
State Priority: The TemplateWidget renders content based on this priority:
isLoading={true}) - Shows spinner, hides everything elseerror is provided) - Shows error message and optional retry buttonchildren content and optional actionsLayout Structure:
┌─────────────────────────────────────┐
│ Header (if showHeader=true) │
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ Title │ │ Header Actions │ │
│ └─────────────┘ └─────────────────┘ │
├─────────────────────────────────────┤
│ │
│ Content Area │
│ (children | loading | error) │
│ │
├─────────────────────────────────────┤
│ Actions (if provided) │
└─────────────────────────────────────┘
Add your widget type to the constants file:
// ui/features/widget_dashboard/react/constants.ts
export const WIDGET_TYPES = {
COURSE_WORK_SUMMARY: 'course_work_summary',
MY_WIDGET: 'my_widget', // Add your new widget type here
} as const
Update the widget registry to include your new widget:
// ui/features/widget_dashboard/react/components/WidgetRegistry.ts
import MyWidget from './widgets/MyWidget' // Import your widget
const widgetRegistry: WidgetRegistry = {
[WIDGET_TYPES.COURSE_WORK_SUMMARY]: {
component: CourseWorkSummaryWidget,
displayName: "Today's course work",
description: 'Shows summary of upcoming assignments and course work',
},
[WIDGET_TYPES.MY_WIDGET]: {
component: MyWidget,
displayName: 'My Custom Widget',
description: 'A custom widget that demonstrates the widget system',
},
}
The registry entry includes:
component: Your React componentdisplayName: Human-readable name for the widgetdescription: What the widget does (useful for admin interfaces later)Add your widget to the default configuration:
// ui/features/widget_dashboard/react/constants.ts
export const DEFAULT_WIDGET_CONFIG = {
columns: 3, // 3-column grid layout
widgets: [
{
id: 'course-work-widget',
type: WIDGET_TYPES.COURSE_WORK_SUMMARY,
position: {col: 1, row: 1}, // Column 1, Row 1
size: {width: 2, height: 1}, // Spans 2 columns, 1 row
title: "Today's course work",
},
{
id: 'my-custom-widget', // Unique identifier
type: WIDGET_TYPES.MY_WIDGET, // References your widget type
position: {col: 3, row: 1}, // Column 3, Row 1 (right side)
size: {width: 1, height: 1}, // Single column, single row
title: 'My Widget Title', // Will be displayed in header
},
],
}
Create comprehensive tests following existing patterns:
// ui/features/widget_dashboard/react/components/widgets/MyWidget/__tests__/MyWidget.test.tsx
import React from 'react'
import {render, screen, fireEvent} from '@testing-library/react'
import MyWidget from '../MyWidget'
import type {BaseWidgetProps} from '../../../../types'
import type {Widget} from '../../../../types'
const mockWidget: Widget = {
id: 'test-my-widget',
type: 'my_widget',
position: {col: 1, row: 1},
size: {width: 1, height: 1},
title: 'Test My Widget',
}
const buildDefaultProps = (overrides: Partial<BaseWidgetProps> = {}): BaseWidgetProps => {
return {
widget: mockWidget,
...overrides,
}
}
describe('MyWidget', () => {
it('renders widget content', () => {
render(<MyWidget {...buildDefaultProps()} />)
expect(screen.getByText('This is my custom widget content')).toBeInTheDocument()
expect(screen.getByRole('button', {name: 'Widget Action'})).toBeInTheDocument()
})
it('handles loading state', () => {
render(<MyWidget {...buildDefaultProps({isLoading: true})} />)
expect(screen.getByText('Loading widget data...')).toBeInTheDocument()
})
it('handles error state', () => {
const onRetry = vi.fn()
render(<MyWidget {...buildDefaultProps({error: 'Failed to load', onRetry})} />)
expect(screen.getByText('Failed to load')).toBeInTheDocument()
expect(screen.getByRole('button', {name: 'Retry'})).toBeInTheDocument()
})
})
After creating your widget, run the tests to ensure everything works:
# Run your specific widget tests
npm test -- ui/features/widget_dashboard/react/components/widgets/MyWidget/__tests__/MyWidget.test.tsx
# Run all widget dashboard tests
npm test -- ui/features/widget_dashboard/
# Check TypeScript compilation
yarn check:ts
The dashboard uses CSS Grid with the following concepts:
{col: 3, row: 1} means column 3, row 1{width: 2, height: 1} means spans 2 columns, 1 row height// Full width widget at top
position: {col: 1, row: 1}, size: {width: 3, height: 1}
// Left side widget
position: {col: 1, row: 2}, size: {width: 1, height: 1}
// Right side widget (spans 2 columns)
position: {col: 2, row: 2}, size: {width: 2, height: 1}
By extending TemplateWidget, your widget automatically gets:
interface BaseWidgetProps {
widget: Widget
isLoading?: boolean
error?: string | null
onRetry?: () => void
}
interface Widget {
id: string
type: string
position: WidgetPosition
size: WidgetSize
title: string
}
interface WidgetRenderer {
component: React.ComponentType<BaseWidgetProps>
displayName: string
description: string
}
The widget dashboard includes comprehensive test coverage:
Run the test suite with:
npm test -- ui/features/widget_dashboard/
When developing widgets, your component only needs to focus on its core functionality. The TemplateWidget base handles all common UI patterns, state management, and user interactions, allowing you to concentrate on delivering value-specific features.
FAQs
A flexible, extensible dashboard system for Canvas LMS that allows students to customize their learning experience with various widgets.
We found that @instructure/platform-widget-dashboard demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
/Security News
Mixed-script homoglyphs and a lookalike domain mimic imToken’s import flow to capture mnemonics and private keys.

Security News
Latio’s 2026 report recognizes Socket as a Supply Chain Innovator and highlights our work in 0-day malware detection, SCA, and auto-patching.

Company News
Join Socket for live demos, rooftop happy hours, and one-on-one meetings during BSidesSF and RSA 2026 in San Francisco.