|
| 1 | +--- |
| 2 | +id: Components-InlineSvg |
| 3 | +title: InlineSvg |
| 4 | +description: Render arbitrary SVG markup with XSS protection |
| 5 | +order: 999 |
| 6 | +menu: |
| 7 | + - Components |
| 8 | + - Media |
| 9 | + - InlineSvg |
| 10 | +tags: |
| 11 | + - component |
| 12 | + - svg |
| 13 | + - icon |
| 14 | + - inline |
| 15 | + - security |
| 16 | +--- |
| 17 | + |
| 18 | +# InlineSvg |
| 19 | + |
| 20 | +The `InlineSvg` component allows you to render arbitrary SVG markup as an icon with built-in XSS protection. It sanitizes the provided SVG string to remove potentially dangerous elements and attributes before rendering. |
| 21 | + |
| 22 | +## Features |
| 23 | + |
| 24 | +- **Security First**: Automatic sanitization of SVG content to prevent XSS attacks |
| 25 | +- **API Compatible**: Drop-in replacement for existing InlineSvg implementations |
| 26 | +- **Accessibility**: Support for title and description elements |
| 27 | +- **Consistent Styling**: Uses the same recipe system as the Icon component |
| 28 | +- **Performance**: Memoized sanitization for optimal re-renders |
| 29 | +- **Zero External Dependencies**: Uses native browser APIs for parsing and sanitization |
| 30 | + |
| 31 | +## Basic Usage |
| 32 | + |
| 33 | +```jsx-live |
| 34 | +const App = () => { |
| 35 | + const svgData = `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> |
| 36 | + <path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 |
| 37 | + 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 |
| 38 | + 3.78-3.4 6.86-8.55 11.54L12 21.35z" fill="currentColor"/> |
| 39 | + </svg>`; |
| 40 | + |
| 41 | + return ( |
| 42 | + <InlineSvg data={svgData} size="md" /> |
| 43 | + ); |
| 44 | +}; |
| 45 | +``` |
| 46 | + |
| 47 | +## Sizes |
| 48 | + |
| 49 | +### Predefined Sizes |
| 50 | + |
| 51 | +The component supports all standard icon sizes through the `size` prop: |
| 52 | + |
| 53 | +```jsx-live |
| 54 | +const App = () => { |
| 55 | + const svgData = `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> |
| 56 | + <path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 |
| 57 | + 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 |
| 58 | + 3.78-3.4 6.86-8.55 11.54L12 21.35z" fill="currentColor"/> |
| 59 | + </svg>`; |
| 60 | + |
| 61 | + return ( |
| 62 | + <Flex gap="400" align="center"> |
| 63 | + <InlineSvg data={svgData} size="2xs" /> |
| 64 | + <InlineSvg data={svgData} size="xs" /> |
| 65 | + <InlineSvg data={svgData} size="sm" /> |
| 66 | + <InlineSvg data={svgData} size="md" /> |
| 67 | + <InlineSvg data={svgData} size="lg" /> |
| 68 | + <InlineSvg data={svgData} size="xl" /> |
| 69 | + </Flex> |
| 70 | + ); |
| 71 | +}; |
| 72 | +``` |
| 73 | + |
| 74 | +### Custom Sizes |
| 75 | + |
| 76 | +For custom sizing, you can use the `boxSize`, `width`, or `height` props with any valid CSS value or design token: |
| 77 | + |
| 78 | +```jsx-live |
| 79 | +const App = () => { |
| 80 | + const svgData = `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> |
| 81 | + <path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 |
| 82 | + 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 |
| 83 | + 3.78-3.4 6.86-8.55 11.54L12 21.35z" fill="currentColor"/> |
| 84 | + </svg>`; |
| 85 | + |
| 86 | + return ( |
| 87 | + <Flex gap="400" align="center"> |
| 88 | + <InlineSvg data={svgData} boxSize="800"/> |
| 89 | + <InlineSvg data={svgData} boxSize="1600"/> |
| 90 | + <InlineSvg data={svgData} width="800" height="800"/> |
| 91 | + </Flex> |
| 92 | + ); |
| 93 | +}; |
| 94 | +``` |
| 95 | + |
| 96 | +## Colors |
| 97 | + |
| 98 | +Apply colors using design tokens: |
| 99 | + |
| 100 | +```jsx-live |
| 101 | +const App = () => { |
| 102 | + const svgData = `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> |
| 103 | + <path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 |
| 104 | + 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 |
| 105 | + 3.78-3.4 6.86-8.55 11.54L12 21.35z" fill="currentColor"/> |
| 106 | + </svg>`; |
| 107 | + |
| 108 | + return ( |
| 109 | + <Flex gap="400" align="center"> |
| 110 | + <InlineSvg data={svgData} size="lg" color="primary.9" /> |
| 111 | + <InlineSvg data={svgData} size="lg" color="neutral.9" /> |
| 112 | + <InlineSvg data={svgData} size="lg" color="info.9" /> |
| 113 | + <InlineSvg data={svgData} size="lg" color="positive.9" /> |
| 114 | + <InlineSvg data={svgData} size="lg" color="warning.9" /> |
| 115 | + <InlineSvg data={svgData} size="lg" color="critical.9" /> |
| 116 | + </Flex> |
| 117 | + ); |
| 118 | +}; |
| 119 | +``` |
| 120 | + |
| 121 | +## Complex SVG |
| 122 | + |
| 123 | +The component preserves complex SVG structures including groups, multiple paths, and transformations: |
| 124 | + |
| 125 | +```jsx-live |
| 126 | +const App = () => { |
| 127 | + const svgData = `<svg fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"> |
| 128 | + <path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/> |
| 129 | + <polyline points="7.5 4.21 12 6.81 16.5 4.21"/> |
| 130 | + <polyline points="7.5 19.79 7.5 14.6 3 12"/> |
| 131 | + <polyline points="21 12 16.5 14.6 16.5 19.79"/> |
| 132 | + <polyline points="3.27 6.96 12 12.01 20.73 6.96"/> |
| 133 | + <line x1="12" x2="12" y1="22.08" y2="12"/> |
| 134 | +</svg>`; |
| 135 | + |
| 136 | + return ( |
| 137 | + <InlineSvg data={svgData} size="xl" color="primary.9" /> |
| 138 | + ); |
| 139 | +}; |
| 140 | +``` |
| 141 | + |
| 142 | +## Multi-Color SVG |
| 143 | + |
| 144 | +SVGs with inline colors are preserved: |
| 145 | + |
| 146 | +```jsx-live |
| 147 | +const App = () => { |
| 148 | + const svgData = `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> |
| 149 | + <rect x="3" y="3" width="18" height="18" rx="2" fill="#e11d48"/> |
| 150 | + <circle cx="12" cy="12" r="5" fill="#10b981"/> |
| 151 | + <path d="M12 9v6M9 12h6" stroke="white" stroke-width="2"/> |
| 152 | +</svg>`; |
| 153 | + |
| 154 | + return ( |
| 155 | + <InlineSvg data={svgData} size="xl" /> |
| 156 | + ); |
| 157 | +}; |
| 158 | +``` |
| 159 | + |
| 160 | +## Security |
| 161 | + |
| 162 | +The component automatically sanitizes potentially dangerous content: |
| 163 | + |
| 164 | +- Removes `<script>` tags |
| 165 | +- Removes event handlers (onclick, onload, etc.) |
| 166 | +- Removes `<style>` tags |
| 167 | +- Sanitizes URLs in href attributes (blocks javascript:, data:, etc.) |
| 168 | +- Removes other potentially dangerous elements |
| 169 | + |
| 170 | +```jsx-live |
| 171 | +const App = () => { |
| 172 | + // This malicious SVG content will be sanitized |
| 173 | + const maliciousSvg = `<svg fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" onclick="alert('XSS')" onLoad="alert('XSS2')"> |
| 174 | + <path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" onmouseover="alert('XSS4')"/> |
| 175 | + <polyline points="7.5 4.21 12 6.81 16.5 4.21"/> |
| 176 | + <polyline points="7.5 19.79 7.5 14.6 3 12"/> |
| 177 | + <polyline points="21 12 16.5 14.6 16.5 19.79"/> |
| 178 | + <polyline points="3.27 6.96 12 12.01 20.73 6.96"/> |
| 179 | + <line x1="12" x2="12" y1="22.08" y2="12"/> |
| 180 | + <style>body { display: none; }</style> |
| 181 | + <script>alert('XSS3')</script> |
| 182 | +</svg>`; |
| 183 | + |
| 184 | + return ( |
| 185 | + <Stack gap="400"> |
| 186 | + <Text>The following SVG has malicious content that gets sanitized:</Text> |
| 187 | + <InlineSvg data={maliciousSvg} size="lg" /> |
| 188 | + <Text fontSize="sm" color="neutral.9"> |
| 189 | + Script tags, event handlers, and style tags are automatically removed |
| 190 | + </Text> |
| 191 | + </Stack> |
| 192 | + ); |
| 193 | +}; |
| 194 | +``` |
| 195 | + |
| 196 | +## Specs |
| 197 | + |
| 198 | + |
| 199 | +## Props |
| 200 | +<PropsTable id="InlineSvg" /> |
| 201 | + |
| 202 | +## Accessibility |
| 203 | + |
| 204 | +The SVG is rendered with `role="presentation"` as it's intended for decorative use. If you need accessible SVGs, include `<title>` and `<desc>` elements directly in your SVG markup - they will be preserved during sanitization. |
| 205 | + |
| 206 | +## Security Considerations |
| 207 | + |
| 208 | +The component provides comprehensive XSS protection: |
| 209 | + |
| 210 | +### What Gets Removed |
| 211 | + |
| 212 | +- All JavaScript execution vectors (`<script>`, event handlers) |
| 213 | +- Style injection attempts (`<style>` tags, style attributes) |
| 214 | +- Dangerous protocols in URLs (`javascript:`, `data:`, etc.) |
| 215 | +- Potentially dangerous elements (`<iframe>`, `<embed>`, etc.) |
| 216 | + |
| 217 | +### What Gets Preserved |
| 218 | + |
| 219 | +- SVG structure elements (`<g>`, `<path>`, `<circle>`, etc.) |
| 220 | +- Visual attributes (fill, stroke, transform, etc.) |
| 221 | +- Accessibility elements (`<title>`, `<desc>`) |
| 222 | +- Safe URLs (http:, https:, relative paths, fragments) |
| 223 | + |
| 224 | +## Best Practices |
| 225 | + |
| 226 | +1. **Always provide a title** for non-decorative SVGs |
| 227 | +2. **Use design tokens** for colors to maintain consistency |
| 228 | +3. **Test with real SVG data** from your actual use cases |
| 229 | +4. **Validate SVG markup** before storing in your database |
| 230 | +5. **Consider performance** for very large or complex SVGs |
0 commit comments