Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions packages/grafana-data/src/themes/createColors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,9 @@ class LightColors implements ThemeColorsBase<Partial<ThemeRichColor>> {
};

border = {
weak: '#e3e0e7',
medium: '#d0ccd5',
strong: '#bdb9c2',
weak: '#ebe9ee',
medium: '#dcd9e1',
strong: '#c4c0cb',
Comment on lines +195 to +197

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep light-theme control borders contrast-compliant.

createComponents.ts (Line 70-73) applies colors.border.medium to input borders while colors.background.primary is now #ffffff. The new #dcd9e1 border has only about 1.4:1 contrast against that background. border.weak and border.strong are also below 3:1. These values can make normal inputs and panel boundaries difficult to identify. Use contrast-tested control borders, or separate decorative weak dividers from control and panel borders.

Also applies to: 231-236

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/grafana-data/src/themes/createColors.ts` around lines 195 - 197,
Update the light-theme border values in createColors so colors.border.medium,
and any border.weak/strong values used for controls or panels, meet the required
contrast against colors.background.primary (`#ffffff`). Use contrast-tested colors
or separate decorative weak-divider values from control and panel border tokens
while preserving decorative divider styling.

};

secondary = {
Expand Down Expand Up @@ -228,10 +228,12 @@ class LightColors implements ThemeColorsBase<Partial<ThemeRichColor>> {
text: '#ab6400',
};

// Panels sit on a pure-white surface floating over a soft mauve canvas so the
// card edges read cleanly, matching the Carrot UI light surfaces.
background = {
primary: '#faf8fb',
canvas: '#e9e7ed',
secondary: '#fdfdfe',
primary: '#ffffff',
canvas: '#f5f3f7',
secondary: '#faf9fb',
};

action = {
Expand Down
6 changes: 4 additions & 2 deletions packages/grafana-data/src/themes/createComponents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,11 @@ export interface ThemeComponents {
}

export function createComponents(colors: ThemeColors, shadows: ThemeShadows): ThemeComponents {
// Roomier panel chrome (12px gutters, 40px header) so charts breathe the way
// they do in the Carrot UI / Vercel card language instead of hugging the border.
const panel = {
padding: 1,
headerHeight: 4,
padding: 1.5,
headerHeight: 5,
background: colors.background.primary,
borderColor: colors.border.weak,
boxShadow: 'none',
Expand Down
14 changes: 8 additions & 6 deletions packages/grafana-data/src/themes/createShadows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,19 @@ export function createShadows(colors: ThemeColors): ThemeShadows {
// Shadow base colours are derived from the canvas background of each mode so
// they harmonise with the mauve-tinted Carrot palette instead of using raw black.
// Dark canvas #121014 → rgb(18, 16, 20) | Light text.primary #211f24 → rgb(33, 31, 36)
// Layered, low-opacity elevation in the Vercel/Geist style: a hairline contact
// shadow stacked with a wider ambient shadow instead of one heavy blur.
if (colors.mode === 'dark') {
return {
z1: '0px 1px 2px rgba(18, 16, 20, 0.8)',
z2: '0px 4px 8px rgba(18, 16, 20, 0.75)',
z3: '0px 8px 24px rgba(18, 16, 20, 0.9)',
z1: '0px 1px 2px rgba(0, 0, 0, 0.45)',
z2: '0px 2px 4px rgba(0, 0, 0, 0.35), 0px 8px 16px rgba(0, 0, 0, 0.4)',
z3: '0px 4px 8px rgba(0, 0, 0, 0.4), 0px 16px 32px rgba(0, 0, 0, 0.5)',
};
}

return {
z1: '0px 1px 2px rgba(33, 31, 36, 0.12)',
z2: '0px 4px 8px rgba(33, 31, 36, 0.15)',
z3: '0px 13px 20px 1px rgba(33, 31, 36, 0.12)',
z1: '0px 1px 2px rgba(33, 31, 36, 0.06)',
z2: '0px 1px 2px rgba(33, 31, 36, 0.06), 0px 4px 12px rgba(33, 31, 36, 0.08)',
z3: '0px 2px 4px rgba(33, 31, 36, 0.06), 0px 12px 32px rgba(33, 31, 36, 0.12)',
};
}
28 changes: 16 additions & 12 deletions packages/grafana-data/src/themes/createTypography.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,12 @@ export function createTypography(colors: ThemeColors, typographyInput: ThemeTypo
fontFamilyMonospace = defaultFontFamilyMonospace,
// The default font size of the Material Specification.
fontSize = 14, // px
fontWeightLight = 200,
fontWeightRegular = 300,
fontWeightMedium = 400,
fontWeightBold = 500,
// Geist/Vercel-style weight ramp. The previous 200/300/400/500 ramp rendered
// body copy as Light, which looked washed out against the Carrot UI palette.
fontWeightLight = 300,
fontWeightRegular = 400,
fontWeightMedium = 500,
fontWeightBold = 600,
// Tell Grafana-UI what's the font-size on the html element.
// 16px is the default font-size used by browsers.
htmlFontSize = 16,
Expand Down Expand Up @@ -111,14 +113,16 @@ export function createTypography(colors: ThemeColors, typographyInput: ThemeTypo

// All our fonts/line heights should be integer multiples of 2 to prevent issues with alignment
const variants = {
h1: buildVariant(fontWeightRegular, 28, 32, -0.25),
h2: buildVariant(fontWeightRegular, 24, 28, 0),
h3: buildVariant(fontWeightRegular, 22, 24, 0),
h4: buildVariant(fontWeightRegular, 18, 22, 0.25),
h5: buildVariant(fontWeightRegular, 16, 22, 0),
h6: buildVariant(fontWeightMedium, 14, 22, 0.15),
body: buildVariant(fontWeightRegular, fontSize, 22, 0.15),
bodySmall: buildVariant(fontWeightRegular, 12, 18, 0.15),
// Headings use negative tracking (Vercel/Geist convention) and a heavier weight
// so panel titles and section headers read as deliberate UI chrome.
h1: buildVariant(fontWeightBold, 28, 32, -0.6),
h2: buildVariant(fontWeightBold, 24, 28, -0.5),
h3: buildVariant(fontWeightMedium, 22, 24, -0.4),
h4: buildVariant(fontWeightMedium, 18, 22, -0.3),
h5: buildVariant(fontWeightMedium, 16, 22, -0.2),
h6: buildVariant(fontWeightMedium, 14, 22, -0.1),
body: buildVariant(fontWeightRegular, fontSize, 22, 0),
bodySmall: buildVariant(fontWeightRegular, 12, 18, 0),
code: { ...buildVariant(fontWeightRegular, 14, 16, 0.15), fontFamily: fontFamilyMonospace },
};

Expand Down
1 change: 1 addition & 0 deletions packages/grafana-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"@grafana/faro-web-sdk": "^1.3.6",
"@grafana/schema": "11.3.0-pre",
"@hello-pangea/dnd": "16.6.0",
"@heroicons/react": "2.2.0",
"@leeoniya/ufuzzy": "1.0.14",
"@monaco-editor/react": "4.6.0",
"@popperjs/core": "2.11.8",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,6 @@ const NarrowScreenForm = (props: FormProps) => {
weekStart={weekStart}
/>
</div>

</div>
)}
</fieldset>
Expand Down Expand Up @@ -198,7 +197,6 @@ const FullScreenForm = (props: FormProps) => {
weekStart={weekStart}
/>
</div>

</>
);
};
Expand Down Expand Up @@ -254,11 +252,16 @@ const useTimeOption = (raw: RawTimeRange, quickOptions: TimeOption[]): TimeOptio

const getStyles = stylesFactory((theme: GrafanaTheme2, isReversed, hideQuickRanges, isContainerTall, isFullscreen) => {
return {
// Carrot UI popover surface: soft 12px radius, hairline border and layered
// elevation so the panel floats above the dashboard rather than boxing it in.
// NOTE: no `overflow: hidden` here — the time zone / fiscal year selects render
// their menus inline (menuShouldPortal={false}), so clipping the container would
// crop the open dropdown list. Corner bleed is handled by the footer's own radius.
container: css({
background: theme.colors.background.primary,
boxShadow: theme.shadows.z3,
width: `${isFullscreen ? '546px' : '262px'}`,
borderRadius: theme.shape.borderRadius(),
borderRadius: theme.shape.borderRadius(3),
border: `1px solid ${theme.colors.border.weak}`,
[`${isReversed ? 'left' : 'right'}`]: 0,
}),
Expand Down Expand Up @@ -286,7 +289,8 @@ const getStyles = stylesFactory((theme: GrafanaTheme2, isReversed, hideQuickRang
flexDirection: 'column',
}),
timeRangeFilter: css({
padding: theme.spacing(1),
padding: theme.spacing(1.5),
borderBottom: `1px solid ${theme.colors.border.weak}`,
}),
spacing: css({
marginTop: '16px',
Expand Down Expand Up @@ -325,12 +329,11 @@ const getNarrowScreenStyles = (theme: GrafanaTheme2) => ({

const getFullScreenStyles = (theme: GrafanaTheme2, hideQuickRanges?: boolean) => ({
container: css({
paddingTop: '9px',
paddingLeft: '11px',
paddingRight: !hideQuickRanges ? '20%' : '11px',
padding: theme.spacing(1.5),
paddingRight: !hideQuickRanges ? '20%' : theme.spacing(1.5),
}),
title: css({
marginBottom: '11px',
marginBottom: theme.spacing(1.5),
}),
recent: css({
flexGrow: 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,26 +169,39 @@ export const TimePickerFooter = (props: Props) => {

const getStyle = stylesFactory((theme: GrafanaTheme2) => {
return {
// Footer reads as a distinct utility bar: recessed surface, hairline top rule.
// `:last-child` keeps the rounding on whichever block actually ends the popover
// (this bar when collapsed, the edit panel when the settings are expanded).
container: css({
borderTop: `1px solid ${theme.colors.border.weak}`,
padding: '11px',
background: theme.colors.background.secondary,
padding: theme.spacing(1.5),
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
fontSize: '14px',
fontSize: theme.typography.bodySmall.fontSize,
lineHeight: '20px',
'&:last-child': {
borderBottomLeftRadius: theme.shape.borderRadius(3),
borderBottomRightRadius: theme.shape.borderRadius(3),
},
'& button': {
borderRadius: '6px',
borderRadius: theme.shape.radius.default,
},
}),
editContainer: css({
borderTop: `1px solid ${theme.colors.border.weak}`,
padding: '11px',
background: theme.colors.background.secondary,
padding: theme.spacing(1.5),
justifyContent: 'space-between',
alignItems: 'center',
fontSize: '14px',
fontSize: theme.typography.bodySmall.fontSize,
lineHeight: '20px',
// Round the trailing corners to match the popover instead of relying on the
// parent clipping, which would crop the inline select menus.
borderBottomLeftRadius: theme.shape.borderRadius(3),
borderBottomRightRadius: theme.shape.borderRadius(3),
}),
spacer: css({
marginLeft: '7px',
Expand Down Expand Up @@ -216,9 +229,22 @@ const getStyle = stylesFactory((theme: GrafanaTheme2) => {
// orange brand gradient) with a neutral gray for the time-zone /
// fiscal-year tabs inside the time-range picker only.
tabsOverride: css({
'button[role="tab"][aria-selected="true"]::before, a[role="tab"][aria-selected="true"]::before': {
'[role="tab"][aria-selected="true"]': {
// The shared Tab sets `overflow: hidden` on the active state, which clips
// its own rounded underline into a boxed outline around the tab. Reset it
// so only the underline shows.
overflow: 'visible',
border: 'none',
boxShadow: 'none',
},

'[role="tab"][aria-selected="true"]::before': {
backgroundImage: 'none',
backgroundColor: theme.colors.border.strong,
backgroundColor: theme.colors.text.primary,
// A slim square-cut rule reads as an underline; the inherited 4px/6px-radius
// bar looked like a stray border sitting under the label.
height: '2px',
borderRadius: 0,
},
}),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ import { useStyles2 } from '../../../themes';

const getStyles = (theme: GrafanaTheme2) => {
return {
// Section labels read as small-caps eyebrows (Carrot UI convention) so the
// quick-range values below them stay the dominant text in the popover.
text: css({
fontSize: theme.typography.size.md,
fontSize: theme.typography.size.xs,
fontWeight: theme.typography.fontWeightMedium,
color: theme.colors.text.primary,
textTransform: 'uppercase',
letterSpacing: '0.06em',
color: theme.colors.text.secondary,
margin: 0,
display: 'flex',
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { css } from '@emotion/css';
import { useRef, ReactNode } from 'react';

import { TimeOption } from '@grafana/data';
import { GrafanaTheme2, TimeOption } from '@grafana/data';

import { useStyles2 } from '../../../themes';
import { t } from '../../../utils/i18n';
Expand Down Expand Up @@ -82,12 +82,12 @@ function isEqual(x: TimeOption, y?: TimeOption): boolean {
return y.from === x.from && y.to === x.to;
}

const getStyles = () => ({
const getStyles = (theme: GrafanaTheme2) => ({
title: css({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '8px 16px 5px 9px',
padding: theme.spacing(1.5, 2, 0.75, 2),
}),
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,23 @@ import { getFocusStyles } from '../../../themes/mixins';

const getStyles = (theme: GrafanaTheme2) => {
return {
// Carrot UI list rows: inset pills with a rounded hover/selected surface
// instead of full-bleed bars butting against the popover edges.
container: css({
display: 'flex',
alignItems: 'center',
flexDirection: 'row-reverse',
justifyContent: 'space-between',
padding: theme.spacing(0, 1),
}),
// Selected uses the neutral secondary surface rather than the brand orange:
// an orange fill fights the panel accents and hurts label contrast.
selected: css({
background: theme.colors.secondary.main,
color: theme.colors.secondary.text,
fontWeight: theme.typography.fontWeightMedium,
'& label, & label:hover': {
background: theme.colors.secondary.main,
color: theme.colors.text.primary,
fontWeight: theme.typography.fontWeightMedium,
},
}),
radio: css({
opacity: 0,
Expand All @@ -30,11 +37,17 @@ const getStyles = (theme: GrafanaTheme2) => {
cursor: 'pointer',
flex: 1,
padding: theme.spacing(0.75, 1.25),
fontSize: '14px',
borderRadius: theme.shape.radius.default,
fontSize: theme.typography.bodySmall.fontSize,
lineHeight: '20px',
color: theme.colors.text.secondary,
transition: theme.transitions.create(['background-color', 'color'], {
duration: theme.transitions.duration.shortest,
}),

'&:hover': {
background: theme.colors.action.hover,
color: theme.colors.text.primary,
cursor: 'pointer',
},
}),
Expand Down
Loading